1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-26 22:42:50 -05:00

Compare commits

..

42 Commits

Author SHA1 Message Date
bbedward
9d1d73a845 dock: some cleanup to overflow 2026-01-26 16:05:22 -05:00
purian23
14a3a0ae55 feat: Dock overflow & New setting options 2026-01-26 15:25:45 -05:00
bbedward
2263338878 dankbar: account for outlineThickness in margins
settings: dont clear caches or apply on startup
2026-01-26 14:19:26 -05:00
bbedward
26bc5425d3 displays: fix vrr=0 setting on hyprland 2026-01-26 11:00:37 -05:00
Karan Singh
38b4d1dc95 Disable VRR in hyprland configuration (#1509)
VRR disabled by default.
2026-01-26 10:57:34 -05:00
bbedward
3aaca7ff39 theme: allow overriding color center theme 2026-01-26 09:18:14 -05:00
bbedward
83d9808536 workspaces: add icon size offset 2026-01-25 22:49:46 -05:00
bbedward
ad458dfece clock: fix no shifting logic 2026-01-25 15:53:01 -05:00
bbedward
8f6fe7ed2b i18n: sync 2026-01-25 14:31:28 -05:00
bbedward
419a692593 update template for feature request 2026-01-25 14:23:05 -05:00
bbedward
03fdf795e0 launcher v2: general styling fixes
- scrollbar
- footer alignment
- radii
- hover colors
2026-01-25 14:21:03 -05:00
bbedward
832807a217 desktop clock: general scaling and stylng fixes for digital variant 2026-01-25 13:30:11 -05:00
Yamada.Kazuyoshi
f7df3b2a68 Fixed an issue where the UI width was shifted due to the clock widget when using non-monospaced fonts. (#1491) 2026-01-24 23:09:20 -05:00
bbedward
0d03e73595 fix vesktop theme name 2026-01-24 22:53:37 -05:00
bbedward
c5ae1a77d3 settings: sidebar scaling improvements 2026-01-24 22:51:59 -05:00
bbedward
5f16624000 misc: fix some various scaling issues with fonts
fixes #1268
2026-01-24 22:27:23 -05:00
purian23
80025804ab theme: Tweaks to Auto Color Mode 2026-01-24 20:43:45 -05:00
bbedward
028d3b4e61 workspaces: fix index numbers with show apps on vBar + animation 2026-01-24 20:31:45 -05:00
purian23
9cce5ccfe6 autoThemeMode: Add transition time & layout update 2026-01-24 19:33:37 -05:00
purian23
a260b8060e Merge branch 'master' into auto-theme 2026-01-24 18:19:13 -05:00
purian23
f945307232 cleanup: Auto theme switcher 2026-01-24 17:48:34 -05:00
bbedward
8f44d52cb2 launcher v2: allow categories in plugins 2026-01-24 16:58:55 -05:00
purian23
3413cb7b89 feat: Create new Auto theme mode based on region / time of day 2026-01-24 16:38:45 -05:00
bbedward
4e3b24ffbb settings: migrate vpnLastConnected to session
fixes #1488
2026-01-24 16:08:15 -05:00
bbedward
03cfa55e0b ipc: ass toast IPCs
fixes #964
2026-01-24 12:53:51 -05:00
bbedward
a887e60f40 keybinds: fix MangoWC config traversal in provider
fixes #1464
2026-01-24 12:23:59 -05:00
bbedward
816819bf9f dankinstall: fix xero color typo 2026-01-23 23:10:24 -05:00
bbedward
78f3bb3812 dankinstall: support XeroLinux
fixes #1474
2026-01-23 22:39:14 -05:00
bbedward
01d7ed5dd8 launcher v2: ability to toggle visibility in modal 2026-01-23 22:17:35 -05:00
Lucas
50311db280 nix: add qt-imageformats to DMS qml dependencies (#1479)
* nix: add qt-imageformats to DMS qml dependencies

* nix: update flake.lock
2026-01-23 21:53:35 -05:00
bbedward
01b1a276c5 launcher v2: support ScreenCopy in tiles 2026-01-23 21:29:48 -05:00
IChengHo
6d4c31492c fix: pass query string to launcher v2 during IPC toggle (#1477)
Ensure toggleQuery forwards the query parameter to the launcher v2
2026-01-23 19:43:42 -05:00
Jon Rogers
f8c5f07e9f Fix: Add view mode persistence for xdg-open picker modals (#1465)
* fix: Add browserPickerViewMode persistence to settings spec

The BrowserPickerModal (used by xdg-open feature) was not persisting
view mode selection between sessions. While the modal had code to save
the view mode preference, the browserPickerViewMode property was not
registered in SettingsSpec.js, preventing it from being saved to disk.

Added browserPickerViewMode and browserUsageHistory to SettingsSpec.js
to ensure user's view preference (list/grid) is properly persisted.

Fixes view mode reverting to grid after restarting DMS/QuickShell.

* fix: Add view mode persistence for both browser and file pickers

Extended the fix to include both picker modals used by xdg-open:

BrowserPickerModal (URLs):
- Added browserPickerViewMode and browserUsageHistory to SettingsSpec.js
- Already had save logic in BrowserPickerModal.qml

AppPickerModal/filePickerModal (files):
- Added appPickerViewMode and filePickerUsageHistory to SettingsSpec.js
- Added appPickerViewMode and filePickerUsageHistory properties to SettingsData.qml
- Added viewMode binding and onViewModeChanged handler to filePickerModal

Both modals now properly persist user's view preference (list/grid) and
usage history between sessions.

Fixes view mode reverting to default grid after restarting DMS/QuickShell
for both 'dms open https://...' and 'dms open file.pdf' workflows.
2026-01-23 19:39:13 -05:00
Ethan Todd
11e23feb0e lockscreen/greetd: add 0 in front of single digit hours for 12 hour format. greetd: add option to hide profile image (#1247)
* greetd: add lockScreenShowProfileImage option

* lockscreen/greetd: for non 24 hour formats, add 0 in front of single digit hours to ensure that everything is always centered properly - previously, it would only appear centered if on a double digit hour. also add getEffectiveTimeFormat function to GreetdSettings.

* clock: made pad 12 hour formats optional

---------

Co-authored-by: bbedward <bbedward@gmail.com>
2026-01-23 14:47:59 -05:00
bbedward
b4ba2dac37 launcher v2: fix nvidia dgpu race condition 2026-01-23 14:15:46 -05:00
bbedward
d013c3b718 workspace: fix rename modal 2026-01-23 14:03:02 -05:00
Kamil Chmielewski
b3ea28c5c4 feat: add workspace rename dialog (#1429)
* feat: add workspace rename dialog

- Adds a modal dialog to rename the current workspace
- Supports both Niri (via IPC socket) and Hyprland (via hyprctl dispatch)
- Default keybinding: Ctrl+Shift+R to open the dialog
- Pre-fills with current workspace name
- Allows setting empty name to reset to default

* refactor: wrap WorkspaceRenameModal in LazyLoader

Reduces memory footprint when the modal is not in use.
2026-01-23 13:46:34 -05:00
bbedward
775b381987 lock: add disable media player option
fixes #1470
2026-01-23 13:34:25 -05:00
bbedward
3a41f2f1ed greeter+lock: remove random facts
fixes #1475
2026-01-23 13:25:42 -05:00
bbedward
972fc534a4 meta: support async launcher plugins, cached GIFs, paste on launcher v2
action
- Preparations for DankGifSearch plugin
2026-01-23 12:03:05 -05:00
purian23
808ee66e11 feat: AppsDock Widget on the Dankbar
- Pinnable apps independent from the main dock
- Drag & Drop support
2026-01-23 11:49:45 -05:00
bbedward
3936a516f8 lock: fix loginctl lock integration disabled setting
fixes #1471
2026-01-23 09:56:43 -05:00
110 changed files with 12044 additions and 2826 deletions

View File

@@ -1,5 +1,5 @@
name: Feature Request
description: Suggest a new feature or improvement for DMS
description: Suggest a new feature or improvement for DMS. Keep features focused on a single topic with clear benefits, examples, etc. Avoid vague or broad requests, they will be closed.
labels:
- enhancement
body:

View File

@@ -6,6 +6,8 @@ This file is more of a quick reference so I know what to account for before next
- dbus API for plugins, KDEConnect
- new dank16 algorithm
- launcher actions, customize env, args, name, icon
- launcher v2 - omega stuff, GIF search, supa powerful
- dock on bar
# 1.2.0

View File

@@ -91,6 +91,9 @@ bind = SUPER CTRL, up, movetoworkspace, e-1
bind = SUPER CTRL, U, movetoworkspace, e+1
bind = SUPER CTRL, I, movetoworkspace, e-1
# === Workspace Management ===
bind = CTRL SHIFT, R, exec, dms ipc call workspace-rename open
# === Move Workspaces ===
bind = SUPER SHIFT, Page_Down, movetoworkspace, e+1
bind = SUPER SHIFT, Page_Up, movetoworkspace, e-1

View File

@@ -81,7 +81,6 @@ master {
misc {
disable_hyprland_logo = true
disable_splash_rendering = true
vrr = 1
}
# ==================

View File

@@ -133,6 +133,11 @@ binds {
Mod+Ctrl+U { move-column-to-workspace-down; }
Mod+Ctrl+I { move-column-to-workspace-up; }
// === Workspace Management ===
Ctrl+Shift+R hotkey-overlay-title="Rename Workspace" {
spawn "dms" "ipc" "call" "workspace-rename" "open";
}
// === Move Workspaces ===
Mod+Shift+Page_Down { move-workspace-down; }
Mod+Shift+Page_Up { move-workspace-up; }

View File

@@ -41,6 +41,9 @@ func init() {
Register("artix", "#1793D1", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
return NewArchDistribution(config, logChan)
})
Register("XeroLinux", "#888fe2", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
return NewArchDistribution(config, logChan)
})
}
type ArchDistribution struct {

View File

@@ -502,17 +502,17 @@ func (p *MangoWCParser) handleSource(line, baseDir string, keybinds *[]MangoWCKe
p.dmsProcessed = true
}
fullPath := sourcePath
if !filepath.IsAbs(sourcePath) {
fullPath = filepath.Join(baseDir, sourcePath)
}
expanded, err := utils.ExpandPath(fullPath)
expanded, err := utils.ExpandPath(sourcePath)
if err != nil {
return
}
includedBinds, err := p.parseFileWithSource(expanded)
fullPath := expanded
if !filepath.IsAbs(expanded) {
fullPath = filepath.Join(baseDir, expanded)
}
includedBinds, err := p.parseFileWithSource(fullPath)
if err != nil {
return
}
@@ -521,33 +521,10 @@ func (p *MangoWCParser) handleSource(line, baseDir string, keybinds *[]MangoWCKe
}
func (p *MangoWCParser) parseDMSBindsDirectly(dmsBindsPath string) []MangoWCKeyBinding {
data, err := os.ReadFile(dmsBindsPath)
keybinds, err := p.parseFileWithSource(dmsBindsPath)
if err != nil {
return nil
}
prevSource := p.currentSource
p.currentSource = dmsBindsPath
var keybinds []MangoWCKeyBinding
lines := strings.Split(string(data), "\n")
for lineNum, line := range lines {
trimmed := strings.TrimSpace(line)
if !strings.HasPrefix(trimmed, "bind") {
continue
}
kb := p.getKeybindAtLineContent(line, lineNum)
if kb == nil {
continue
}
kb.Source = dmsBindsPath
p.addBind(kb)
keybinds = append(keybinds, *kb)
}
p.currentSource = prevSource
p.dmsProcessed = true
return keybinds
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
serverPlugins "github.com/AvengeMedia/DankMaterialShell/core/internal/server/plugins"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/thememode"
serverThemes "github.com/AvengeMedia/DankMaterialShell/core/internal/server/themes"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
@@ -44,6 +45,15 @@ func RouteRequest(conn net.Conn, req models.Request) {
return
}
if strings.HasPrefix(req.Method, "theme.auto.") {
if themeModeManager == nil {
models.RespondError(conn, req.ID, "theme mode manager not initialized")
return
}
thememode.HandleRequest(conn, req, themeModeManager)
return
}
if strings.HasPrefix(req.Method, "loginctl.") {
if loginctlManager == nil {
models.RespondError(conn, req.ID, "loginctl manager not initialized")

View File

@@ -28,6 +28,7 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/thememode"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlcontext"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
@@ -68,6 +69,7 @@ var evdevManager *evdev.Manager
var clipboardManager *clipboard.Manager
var dbusManager *serverDbus.Manager
var wlContext *wlcontext.SharedContext
var themeModeManager *thememode.Manager
const dbusClientID = "dms-dbus-client"
@@ -380,6 +382,14 @@ func InitializeDbusManager() error {
return nil
}
func InitializeThemeModeManager() error {
manager := thememode.NewManager()
themeModeManager = manager
log.Info("Theme mode automation manager initialized")
return nil
}
func handleConnection(conn net.Conn) {
defer conn.Close()
@@ -457,6 +467,10 @@ func getCapabilities() Capabilities {
caps = append(caps, "clipboard")
}
if themeModeManager != nil {
caps = append(caps, "theme.auto")
}
if dbusManager != nil {
caps = append(caps, "dbus")
}
@@ -519,6 +533,10 @@ func getServerInfo() ServerInfo {
caps = append(caps, "clipboard")
}
if themeModeManager != nil {
caps = append(caps, "theme.auto")
}
if dbusManager != nil {
caps = append(caps, "dbus")
}
@@ -791,6 +809,38 @@ func handleSubscribe(conn net.Conn, req models.Request) {
}()
}
if shouldSubscribe("theme.auto") && themeModeManager != nil {
wg.Add(1)
themeAutoChan := themeModeManager.Subscribe(clientID + "-theme-auto")
go func() {
defer wg.Done()
defer themeModeManager.Unsubscribe(clientID + "-theme-auto")
initialState := themeModeManager.GetState()
select {
case eventChan <- ServiceEvent{Service: "theme.auto", Data: initialState}:
case <-stopChan:
return
}
for {
select {
case state, ok := <-themeAutoChan:
if !ok {
return
}
select {
case eventChan <- ServiceEvent{Service: "theme.auto", Data: state}:
case <-stopChan:
return
}
case <-stopChan:
return
}
}
}()
}
if shouldSubscribe("bluetooth") && bluezManager != nil {
wg.Add(1)
bluezChan := bluezManager.Subscribe(clientID + "-bluetooth")
@@ -1251,6 +1301,9 @@ func cleanupManagers() {
if dbusManager != nil {
dbusManager.Close()
}
if themeModeManager != nil {
themeModeManager.Close()
}
if wlContext != nil {
wlContext.Close()
}
@@ -1346,6 +1399,15 @@ func Start(printDocs bool) error {
log.Info(" wayland.gamma.setGamma - Set gamma value (params: gamma)")
log.Info(" wayland.gamma.setEnabled - Enable/disable gamma control (params: enabled)")
log.Info(" wayland.gamma.subscribe - Subscribe to gamma state changes (streaming)")
log.Info("Theme automation:")
log.Info(" theme.auto.getState - Get current theme automation state")
log.Info(" theme.auto.setEnabled - Enable/disable theme automation (params: enabled)")
log.Info(" theme.auto.setMode - Set automation mode (params: mode [time|location])")
log.Info(" theme.auto.setSchedule - Set time schedule (params: startHour, startMinute, endHour, endMinute)")
log.Info(" theme.auto.setLocation - Set location (params: latitude, longitude)")
log.Info(" theme.auto.setUseIPLocation - Use IP location (params: use)")
log.Info(" theme.auto.trigger - Trigger immediate re-evaluation")
log.Info(" theme.auto.subscribe - Subscribe to theme automation state changes (streaming)")
log.Info("Bluetooth:")
log.Info(" bluetooth.getState - Get current bluetooth state")
log.Info(" bluetooth.startDiscovery - Start device discovery")
@@ -1503,6 +1565,12 @@ func Start(printDocs bool) error {
log.Debugf("WlrOutput manager unavailable: %v", err)
}
if err := InitializeThemeModeManager(); err != nil {
log.Warnf("Theme mode manager unavailable: %v", err)
} else {
notifyCapabilityChange()
}
fatalErrChan := make(chan error, 1)
if wlrOutputManager != nil {
go func() {

View File

@@ -0,0 +1,154 @@
package thememode
import (
"encoding/json"
"fmt"
"net"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
)
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
if manager == nil {
models.RespondError(conn, req.ID, "theme mode manager not initialized")
return
}
switch req.Method {
case "theme.auto.getState":
handleGetState(conn, req, manager)
case "theme.auto.setEnabled":
handleSetEnabled(conn, req, manager)
case "theme.auto.setMode":
handleSetMode(conn, req, manager)
case "theme.auto.setSchedule":
handleSetSchedule(conn, req, manager)
case "theme.auto.setLocation":
handleSetLocation(conn, req, manager)
case "theme.auto.setUseIPLocation":
handleSetUseIPLocation(conn, req, manager)
case "theme.auto.trigger":
handleTrigger(conn, req, manager)
case "theme.auto.subscribe":
handleSubscribe(conn, req, manager)
default:
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
}
}
func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
models.Respond(conn, req.ID, manager.GetState())
}
func handleSetEnabled(conn net.Conn, req models.Request, manager *Manager) {
enabled, err := params.Bool(req.Params, "enabled")
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
manager.SetEnabled(enabled)
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto enabled set"})
}
func handleSetMode(conn net.Conn, req models.Request, manager *Manager) {
mode, err := params.String(req.Params, "mode")
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
if mode != "time" && mode != "location" {
models.RespondError(conn, req.ID, "invalid mode")
return
}
manager.SetMode(mode)
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto mode set"})
}
func handleSetSchedule(conn net.Conn, req models.Request, manager *Manager) {
startHour, err := params.Int(req.Params, "startHour")
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
startMinute, err := params.Int(req.Params, "startMinute")
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
endHour, err := params.Int(req.Params, "endHour")
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
endMinute, err := params.Int(req.Params, "endMinute")
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
if err := manager.ValidateSchedule(startHour, startMinute, endHour, endMinute); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
manager.SetSchedule(startHour, startMinute, endHour, endMinute)
models.Respond(conn, req.ID, manager.GetState())
}
func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) {
lat, err := params.Float(req.Params, "latitude")
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
lon, err := params.Float(req.Params, "longitude")
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
manager.SetLocation(lat, lon)
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto location set"})
}
func handleSetUseIPLocation(conn net.Conn, req models.Request, manager *Manager) {
use, err := params.Bool(req.Params, "use")
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
manager.SetUseIPLocation(use)
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto IP location set"})
}
func handleTrigger(conn net.Conn, req models.Request, manager *Manager) {
manager.TriggerUpdate()
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto update triggered"})
}
func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
clientID := fmt.Sprintf("client-%p", conn)
stateChan := manager.Subscribe(clientID)
defer manager.Unsubscribe(clientID)
initialState := manager.GetState()
if err := json.NewEncoder(conn).Encode(models.Response[State]{
ID: req.ID,
Result: &initialState,
}); err != nil {
return
}
for state := range stateChan {
if err := json.NewEncoder(conn).Encode(models.Response[State]{
Result: &state,
}); err != nil {
return
}
}
}

View File

@@ -0,0 +1,432 @@
package thememode
import (
"errors"
"sync"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
)
const (
defaultStartHour = 18
defaultStartMinute = 0
defaultEndHour = 6
defaultEndMinute = 0
defaultElevationTwilight = -6.0
defaultElevationDaylight = 3.0
)
type Manager struct {
config Config
configMutex sync.RWMutex
state *State
stateMutex sync.RWMutex
subscribers syncmap.Map[string, chan State]
locationMutex sync.RWMutex
cachedIPLat *float64
cachedIPLon *float64
stopChan chan struct{}
updateTrigger chan struct{}
wg sync.WaitGroup
}
func NewManager() *Manager {
m := &Manager{
config: Config{
Enabled: false,
Mode: "time",
StartHour: defaultStartHour,
StartMinute: defaultStartMinute,
EndHour: defaultEndHour,
EndMinute: defaultEndMinute,
ElevationTwilight: defaultElevationTwilight,
ElevationDaylight: defaultElevationDaylight,
},
stopChan: make(chan struct{}),
updateTrigger: make(chan struct{}, 1),
}
m.updateState(time.Now())
m.wg.Add(1)
go m.schedulerLoop()
return m
}
func (m *Manager) GetState() State {
m.stateMutex.RLock()
defer m.stateMutex.RUnlock()
if m.state == nil {
return State{Config: m.getConfig()}
}
stateCopy := *m.state
return stateCopy
}
func (m *Manager) Subscribe(id string) chan State {
ch := make(chan State, 64)
m.subscribers.Store(id, ch)
return ch
}
func (m *Manager) Unsubscribe(id string) {
if val, ok := m.subscribers.LoadAndDelete(id); ok {
close(val)
}
}
func (m *Manager) SetEnabled(enabled bool) {
m.configMutex.Lock()
if m.config.Enabled == enabled {
m.configMutex.Unlock()
return
}
m.config.Enabled = enabled
m.configMutex.Unlock()
m.TriggerUpdate()
}
func (m *Manager) SetMode(mode string) {
m.configMutex.Lock()
if m.config.Mode == mode {
m.configMutex.Unlock()
return
}
m.config.Mode = mode
m.configMutex.Unlock()
m.TriggerUpdate()
}
func (m *Manager) SetSchedule(startHour, startMinute, endHour, endMinute int) {
m.configMutex.Lock()
changed := m.config.StartHour != startHour ||
m.config.StartMinute != startMinute ||
m.config.EndHour != endHour ||
m.config.EndMinute != endMinute
if !changed {
m.configMutex.Unlock()
return
}
m.config.StartHour = startHour
m.config.StartMinute = startMinute
m.config.EndHour = endHour
m.config.EndMinute = endMinute
m.configMutex.Unlock()
m.TriggerUpdate()
}
func (m *Manager) SetLocation(lat, lon float64) {
m.configMutex.Lock()
if m.config.Latitude != nil && m.config.Longitude != nil &&
*m.config.Latitude == lat && *m.config.Longitude == lon && !m.config.UseIPLocation {
m.configMutex.Unlock()
return
}
m.config.Latitude = &lat
m.config.Longitude = &lon
m.config.UseIPLocation = false
m.configMutex.Unlock()
m.locationMutex.Lock()
m.cachedIPLat = nil
m.cachedIPLon = nil
m.locationMutex.Unlock()
m.TriggerUpdate()
}
func (m *Manager) SetUseIPLocation(use bool) {
m.configMutex.Lock()
if m.config.UseIPLocation == use {
m.configMutex.Unlock()
return
}
m.config.UseIPLocation = use
if use {
m.config.Latitude = nil
m.config.Longitude = nil
}
m.configMutex.Unlock()
if use {
m.locationMutex.Lock()
m.cachedIPLat = nil
m.cachedIPLon = nil
m.locationMutex.Unlock()
}
m.TriggerUpdate()
}
func (m *Manager) TriggerUpdate() {
select {
case m.updateTrigger <- struct{}{}:
default:
}
}
func (m *Manager) Close() {
select {
case <-m.stopChan:
return
default:
close(m.stopChan)
}
m.wg.Wait()
m.subscribers.Range(func(key string, ch chan State) bool {
close(ch)
m.subscribers.Delete(key)
return true
})
}
func (m *Manager) schedulerLoop() {
defer m.wg.Done()
var timer *time.Timer
for {
config := m.getConfig()
now := time.Now()
var isLight bool
var next time.Time
if config.Enabled {
isLight, next = m.computeSchedule(now, config)
} else {
m.stateMutex.RLock()
if m.state != nil {
isLight = m.state.IsLight
}
m.stateMutex.RUnlock()
next = now.Add(24 * time.Hour)
}
m.updateStateWithValues(config, isLight, next)
waitDur := time.Until(next)
if !config.Enabled {
waitDur = 24 * time.Hour
}
if waitDur < time.Second {
waitDur = time.Second
}
if timer != nil {
timer.Stop()
}
timer = time.NewTimer(waitDur)
select {
case <-m.stopChan:
timer.Stop()
return
case <-m.updateTrigger:
timer.Stop()
continue
case <-timer.C:
continue
}
}
}
func (m *Manager) updateState(now time.Time) {
config := m.getConfig()
var isLight bool
var next time.Time
if config.Enabled {
isLight, next = m.computeSchedule(now, config)
} else {
m.stateMutex.RLock()
if m.state != nil {
isLight = m.state.IsLight
}
m.stateMutex.RUnlock()
next = now.Add(24 * time.Hour)
}
m.updateStateWithValues(config, isLight, next)
}
func (m *Manager) updateStateWithValues(config Config, isLight bool, next time.Time) {
newState := State{
Config: config,
IsLight: isLight,
NextTransition: next,
}
m.stateMutex.Lock()
if m.state != nil && statesEqual(m.state, &newState) {
m.stateMutex.Unlock()
return
}
m.state = &newState
m.stateMutex.Unlock()
m.notifySubscribers()
}
func (m *Manager) notifySubscribers() {
state := m.GetState()
m.subscribers.Range(func(key string, ch chan State) bool {
select {
case ch <- state:
default:
}
return true
})
}
func (m *Manager) getConfig() Config {
m.configMutex.RLock()
defer m.configMutex.RUnlock()
return m.config
}
func (m *Manager) getLocation(config Config) (*float64, *float64) {
if config.Latitude != nil && config.Longitude != nil {
return config.Latitude, config.Longitude
}
if !config.UseIPLocation {
return nil, nil
}
m.locationMutex.RLock()
if m.cachedIPLat != nil && m.cachedIPLon != nil {
lat, lon := m.cachedIPLat, m.cachedIPLon
m.locationMutex.RUnlock()
return lat, lon
}
m.locationMutex.RUnlock()
lat, lon, err := wayland.FetchIPLocation()
if err != nil {
return nil, nil
}
m.locationMutex.Lock()
m.cachedIPLat = lat
m.cachedIPLon = lon
m.locationMutex.Unlock()
return lat, lon
}
func statesEqual(a, b *State) bool {
if a == nil || b == nil {
return a == b
}
if a.IsLight != b.IsLight || !a.NextTransition.Equal(b.NextTransition) {
return false
}
return a.Config == b.Config
}
func (m *Manager) computeSchedule(now time.Time, config Config) (bool, time.Time) {
if config.Mode == "location" {
return m.computeLocationSchedule(now, config)
}
return computeTimeSchedule(now, config)
}
func computeTimeSchedule(now time.Time, config Config) (bool, time.Time) {
startMinutes := config.StartHour*60 + config.StartMinute
endMinutes := config.EndHour*60 + config.EndMinute
currentMinutes := now.Hour()*60 + now.Minute()
startTime := time.Date(now.Year(), now.Month(), now.Day(), config.StartHour, config.StartMinute, 0, 0, now.Location())
endTime := time.Date(now.Year(), now.Month(), now.Day(), config.EndHour, config.EndMinute, 0, 0, now.Location())
if startMinutes == endMinutes {
next := startTime
if !next.After(now) {
next = next.Add(24 * time.Hour)
}
return true, next
}
if startMinutes < endMinutes {
if currentMinutes < startMinutes {
return true, startTime
}
if currentMinutes >= endMinutes {
return true, startTime.Add(24 * time.Hour)
}
return false, endTime
}
if currentMinutes >= startMinutes {
return false, endTime.Add(24 * time.Hour)
}
if currentMinutes < endMinutes {
return false, endTime
}
return true, startTime
}
func (m *Manager) computeLocationSchedule(now time.Time, config Config) (bool, time.Time) {
lat, lon := m.getLocation(config)
if lat == nil || lon == nil {
currentIsLight := false
m.stateMutex.RLock()
if m.state != nil {
currentIsLight = m.state.IsLight
}
m.stateMutex.RUnlock()
return currentIsLight, now.Add(10 * time.Minute)
}
times, cond := wayland.CalculateSunTimesWithTwilight(*lat, *lon, now, config.ElevationTwilight, config.ElevationDaylight)
if cond != wayland.SunNormal {
if cond == wayland.SunMidnightSun {
return true, startOfNextDay(now)
}
return false, startOfNextDay(now)
}
if now.Before(times.Sunrise) {
return false, times.Sunrise
}
if now.Before(times.Sunset) {
return true, times.Sunset
}
nextDay := startOfNextDay(now)
nextTimes, nextCond := wayland.CalculateSunTimesWithTwilight(*lat, *lon, nextDay, config.ElevationTwilight, config.ElevationDaylight)
if nextCond != wayland.SunNormal {
if nextCond == wayland.SunMidnightSun {
return true, startOfNextDay(nextDay)
}
return false, startOfNextDay(nextDay)
}
return false, nextTimes.Sunrise
}
func startOfNextDay(t time.Time) time.Time {
next := t.Add(24 * time.Hour)
return time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, next.Location())
}
func validateHourMinute(hour, minute int) bool {
if hour < 0 || hour > 23 {
return false
}
if minute < 0 || minute > 59 {
return false
}
return true
}
func (m *Manager) ValidateSchedule(startHour, startMinute, endHour, endMinute int) error {
if !validateHourMinute(startHour, startMinute) || !validateHourMinute(endHour, endMinute) {
return errInvalidTime
}
return nil
}
var errInvalidTime = errors.New("invalid schedule time")

View File

@@ -0,0 +1,23 @@
package thememode
import "time"
type Config struct {
Enabled bool `json:"enabled"`
Mode string `json:"mode"`
StartHour int `json:"startHour"`
StartMinute int `json:"startMinute"`
EndHour int `json:"endHour"`
EndMinute int `json:"endMinute"`
Latitude *float64 `json:"latitude,omitempty"`
Longitude *float64 `json:"longitude,omitempty"`
UseIPLocation bool `json:"useIPLocation"`
ElevationTwilight float64 `json:"elevationTwilight"`
ElevationDaylight float64 `json:"elevationDaylight"`
}
type State struct {
Config Config `json:"config"`
IsLight bool `json:"isLight"`
NextTransition time.Time `json:"nextTransition"`
}

View File

@@ -626,6 +626,7 @@ func (m *Manager) schedulerLoop() {
m.schedule.calcDay = time.Time{}
m.scheduleMutex.Unlock()
m.recalcSchedule(time.Now())
m.updateStateFromSchedule()
m.configMutex.RLock()
enabled := m.config.Enabled
m.configMutex.RUnlock()

View File

@@ -109,8 +109,6 @@ rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
%posttrans
# Signal running DMS instances to reload
pkill -USR1 -x dms >/dev/null 2>&1 || :

View File

@@ -100,8 +100,6 @@ rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
rm -rf %{buildroot}%{_datadir}/quickshell/dms/core
echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
%posttrans
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true

6
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1766651565,
"narHash": "sha256-QEhk0eXgyIqTpJ/ehZKg9IKS7EtlWxF3N7DXy42zPfU=",
"lastModified": 1769018530,
"narHash": "sha256-MJ27Cy2NtBEV5tsK+YraYr2g851f3Fl1LpNHDzDX15c=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "3e2499d5539c16d0d173ba53552a4ff8547f4539",
"rev": "88d3861acdd3d2f0e361767018218e51810df8a1",
"type": "github"
},
"original": {

View File

@@ -47,6 +47,7 @@
kirigami.unwrapped
sonnet
qtmultimedia
qtimageformats
];
in
{

View File

@@ -1,53 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
readonly property var facts: [
"A photon takes 100,000 to 200,000 years bouncing through the Sun's dense core, then races to Earth in just 8 minutes 20 seconds.",
"A teaspoon of neutron star matter would weigh a billion metric tons here on Earth.",
"Right now, 100 trillion solar neutrinos are passing through your body every second.",
"The Sun converts 4 million metric tons of matter into pure energy every second—enough to power Earth for 500,000 years.",
"The universe still glows with leftover heat from the Big Bang—just 2.7 degrees above absolute zero.",
"There's a nebula out there that's actually colder than empty space itself.",
"We've detected black holes crashing together by measuring spacetime stretch by less than 1/10,000th the width of a proton.",
"Fast radio bursts can release more energy in 5 milliseconds than our Sun produces in 3 days.",
"Our galaxy might be crawling with billions of rogue planets drifting alone in the dark.",
"Distant galaxies can move away from us faster than light because space itself is stretching.",
"The edge of what we can see is 46.5 billion light-years away, even though the universe is only 13.8 billion years old.",
"The universe is mostly invisible: 5% regular matter, 27% dark matter, 68% dark energy.",
"A day on Venus lasts longer than its entire year around the Sun.",
"On Mercury, the time between sunrises is 176 Earth days long.",
"In about 4.5 billion years, our galaxy will smash into Andromeda.",
"Most of the gold in your jewelry was forged when neutron stars collided somewhere in space.",
"PSR J1748-2446ad, the fastest spinning star, rotates 716 times per second—its equator moves at 24% the speed of light.",
"Cosmic rays create particles that shouldn't make it to Earth's surface, but time dilation lets them sneak through.",
"Jupiter's magnetic field is so huge that if we could see it, it would look bigger than the Moon in our sky.",
"Interstellar space is so empty it's like a cube 32 kilometers wide containing just a single grain of sand.",
"Voyager 1 is 24 billion kilometers away but won't leave the Sun's gravitational influence for another 30,000 years.",
"Counting to a billion at one number per second would take over 31 years.",
"Space is so vast, even speeding at light-speed, you'd never return past the cosmic horizon.",
"Astronauts on the ISS age about 0.01 seconds less each year than people on Earth.",
"Sagittarius B2, a dust cloud near our galaxy's center, contains ethyl formate—the compound that gives raspberries their flavor and rum its smell.",
"Beyond 16 billion light-years, the cosmic event horizon marks where space expands too fast for light to ever reach us again.",
"Even at light-speed, you'd never catch up to most galaxies—space expands faster.",
"Only around 5% of galaxies are ever reachable—even at light-speed.",
"If the Sun vanished, we'd still orbit it for 8 minutes before drifting away.",
"If a planet 65 million light-years away looked at Earth now, it'd see dinosaurs.",
"Our oldest radio signals will reach the Milky Way's center in 26,000 years.",
"Every atom in your body heavier than hydrogen was forged in the nuclear furnace of a dying star.",
"The Moon moves 3.8 centimeters farther from Earth every year.",
"The universe creates 275 million new stars every single day.",
"Jupiter's Great Red Spot is a storm twice the size of Earth that has been raging for at least 350 years.",
"If you watched someone fall into a black hole, they'd appear frozen at the event horizon forever—time effectively stops from your perspective.",
"The Boötes Supervoid is a cosmic desert 1.8 billion light-years across with 60% fewer galaxies than it should have."
]
function getRandomFact() {
return facts[Math.floor(Math.random() * facts.length)]
}
}

View File

@@ -100,7 +100,8 @@ const DMS_ACTIONS = [
{ id: "spawn dms ipc call hypr openOverview", label: "Hyprland: Open Overview", compositor: "hyprland" },
{ id: "spawn dms ipc call hypr closeOverview", label: "Hyprland: Close Overview", compositor: "hyprland" },
{ id: "spawn dms ipc call wallpaper next", label: "Wallpaper: Next" },
{ id: "spawn dms ipc call wallpaper prev", label: "Wallpaper: Previous" }
{ id: "spawn dms ipc call wallpaper prev", label: "Wallpaper: Previous" },
{ id: "spawn dms ipc call workspace-rename open", label: "Workspace: Rename" }
];
const NIRI_ACTIONS = {

View File

@@ -13,7 +13,7 @@ import "settings/SessionStore.js" as Store
Singleton {
id: root
readonly property int sessionConfigVersion: 2
readonly property int sessionConfigVersion: 3
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
property bool _parseError: false
@@ -82,7 +82,17 @@ Singleton {
property bool nightModeUseIPLocation: false
property string nightModeLocationProvider: ""
property bool themeModeAutoEnabled: false
property string themeModeAutoMode: "time"
property int themeModeStartHour: 18
property int themeModeStartMinute: 0
property int themeModeEndHour: 6
property int themeModeEndMinute: 0
property bool themeModeShareGammaSettings: true
property string themeModeNextTransition: ""
property var pinnedApps: []
property var barPinnedApps: []
property int dockLauncherPosition: 0
property var hiddenTrayIds: []
property var recentColors: []
@@ -108,6 +118,8 @@ Singleton {
property var appOverrides: ({})
property bool searchAppActions: true
property string vpnLastConnected: ""
Component.onCompleted: {
if (!isGreeterMode) {
loadSettings();
@@ -171,7 +183,7 @@ Singleton {
} catch (e) {
_parseError = true;
const msg = e.message;
console.error("SessionData: Failed to parse session.json - file will not be overwritten. Error:", msg);
console.error("SessionData: Failed to parse session.json - file will not be overwritten.");
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse session.json"), msg));
}
}
@@ -185,14 +197,10 @@ Singleton {
_isReadOnly = !writable;
if (_isReadOnly) {
_hasUnsavedChanges = _checkForUnsavedChanges();
if (!wasReadOnly)
console.info("SessionData: session.json is now read-only");
} else {
_loadedSessionSnapshot = getCurrentSessionJson();
_hasUnsavedChanges = false;
if (wasReadOnly)
console.info("SessionData: session.json is now writable");
if (_pendingMigration)
if (wasReadOnly && _pendingMigration)
settingsFile.setText(JSON.stringify(_pendingMigration, null, 2));
}
_pendingMigration = null;
@@ -254,7 +262,7 @@ Singleton {
} catch (e) {
_parseError = true;
const msg = e.message;
console.error("SessionData: Failed to parse session.json - file will not be overwritten. Error:", msg);
console.error("SessionData: Failed to parse session.json - file will not be overwritten.");
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse session.json"), msg));
}
}
@@ -272,7 +280,6 @@ Singleton {
}
function migrateFromUndefinedToV1(settings) {
console.info("SessionData: Migrating configuration from undefined to version 1");
if (typeof SettingsData !== "undefined") {
if (settings.acMonitorTimeout !== undefined) {
SettingsData.set("acMonitorTimeout", settings.acMonitorTimeout);
@@ -447,7 +454,7 @@ Singleton {
}
if (!screen) {
console.warn("SessionData: Screen not found:", screenName);
console.warn("SessionData: Screen not found");
return;
}
@@ -544,7 +551,7 @@ Singleton {
}
if (!screen) {
console.warn("SessionData: Screen not found:", screenName);
console.warn("SessionData: Screen not found");
return;
}
@@ -582,7 +589,7 @@ Singleton {
}
if (!screen) {
console.warn("SessionData: Screen not found:", screenName);
console.warn("SessionData: Screen not found");
return;
}
@@ -620,7 +627,7 @@ Singleton {
}
if (!screen) {
console.warn("SessionData: Screen not found:", screenName);
console.warn("SessionData: Screen not found");
return;
}
@@ -658,7 +665,7 @@ Singleton {
}
if (!screen) {
console.warn("SessionData: Screen not found:", screenName);
console.warn("SessionData: Screen not found");
return;
}
@@ -701,7 +708,6 @@ Singleton {
}
function setNightModeAutoEnabled(enabled) {
console.log("SessionData: Setting nightModeAutoEnabled to", enabled);
nightModeAutoEnabled = enabled;
saveSettings();
}
@@ -737,13 +743,11 @@ Singleton {
}
function setLatitude(lat) {
console.log("SessionData: Setting latitude to", lat);
latitude = lat;
saveSettings();
}
function setLongitude(lng) {
console.log("SessionData: Setting longitude to", lng);
longitude = lng;
saveSettings();
}
@@ -753,6 +757,41 @@ Singleton {
saveSettings();
}
function setThemeModeAutoEnabled(enabled) {
themeModeAutoEnabled = enabled;
saveSettings();
}
function setThemeModeAutoMode(mode) {
themeModeAutoMode = mode;
saveSettings();
}
function setThemeModeStartHour(hour) {
themeModeStartHour = hour;
saveSettings();
}
function setThemeModeStartMinute(minute) {
themeModeStartMinute = minute;
saveSettings();
}
function setThemeModeEndHour(hour) {
themeModeEndHour = hour;
saveSettings();
}
function setThemeModeEndMinute(minute) {
themeModeEndMinute = minute;
saveSettings();
}
function setThemeModeShareGammaSettings(share) {
themeModeShareGammaSettings = share;
saveSettings();
}
function setPinnedApps(apps) {
pinnedApps = apps;
saveSettings();
@@ -784,6 +823,32 @@ Singleton {
return appId && pinnedApps.indexOf(appId) !== -1;
}
function setBarPinnedApps(apps) {
barPinnedApps = apps;
saveSettings();
}
function addBarPinnedApp(appId) {
if (!appId)
return;
var currentPinned = [...barPinnedApps];
if (currentPinned.indexOf(appId) === -1) {
currentPinned.push(appId);
setBarPinnedApps(currentPinned);
}
}
function removeBarPinnedApp(appId) {
if (!appId)
return;
var currentPinned = barPinnedApps.filter(id => id !== appId);
setBarPinnedApps(currentPinned);
}
function isBarPinnedApp(appId) {
return appId && barPinnedApps.indexOf(appId) !== -1;
}
function hideTrayId(trayId) {
if (!trayId)
return;
@@ -976,6 +1041,11 @@ Singleton {
saveSettings();
}
function setVpnLastConnected(uuid) {
vpnLastConnected = uuid || "";
saveSettings();
}
function syncWallpaperForCurrentMode() {
if (!perModeWallpaper)
return;

View File

@@ -133,6 +133,7 @@ Singleton {
property real dockTransparency: 1
property string widgetBackgroundColor: "sch"
property string widgetColorMode: "default"
property string controlCenterTileColorMode: "primary"
property real cornerRadius: 12
property int niriLayoutGapsOverride: -1
property int niriLayoutRadiusOverride: -1
@@ -146,6 +147,7 @@ Singleton {
property bool use24HourClock: true
property bool showSeconds: false
property bool padHours12Hour: false
property bool useFahrenheit: false
property string windSpeedUnit: "kmh"
property bool nightModeEnabled: false
@@ -241,6 +243,7 @@ Singleton {
property bool showWorkspaceApps: false
property bool groupWorkspaceApps: true
property int maxWorkspaceIcons: 3
property int workspaceAppIconSizeOffset: 0
property bool workspaceFollowFocus: false
property bool showOccupiedWorkspacesOnly: false
property bool reverseScrolling: false
@@ -273,6 +276,8 @@ Singleton {
property string spotlightModalViewMode: "list"
property string browserPickerViewMode: "grid"
property var browserUsageHistory: ({})
property string appPickerViewMode: "grid"
property var filePickerUsageHistory: ({})
property bool sortAppsAlphabetically: false
property int appLauncherGridColumns: 4
property bool spotlightCloseNiriOverview: true
@@ -289,13 +294,13 @@ Singleton {
property string _legacyWeatherLocation: "New York, NY"
property string _legacyWeatherCoordinates: "40.7128,-74.0060"
property string _legacyVpnLastConnected: ""
readonly property string weatherLocation: SessionData.weatherLocation
readonly property string weatherCoordinates: SessionData.weatherCoordinates
property bool useAutoLocation: false
property bool weatherEnabled: true
property string networkPreference: "auto"
property string vpnLastConnected: ""
property string iconTheme: "System Default"
property var availableIconThemes: ["System Default"]
@@ -438,6 +443,9 @@ Singleton {
property int dockLauncherLogoSizeOffset: 0
property real dockLauncherLogoBrightness: 0.5
property real dockLauncherLogoContrast: 1
property int dockMaxVisibleApps: 0
property int dockMaxVisibleRunningApps: 0
property bool dockShowOverflowBadge: true
property bool notificationOverlayEnabled: false
property int overviewRows: 2
@@ -452,6 +460,7 @@ Singleton {
property bool lockScreenShowDate: true
property bool lockScreenShowProfileImage: true
property bool lockScreenShowPasswordField: true
property bool lockScreenShowMediaPlayer: true
property bool lockScreenPowerOffMonitorsOnLock: false
property bool enableFprint: false
@@ -998,7 +1007,6 @@ Singleton {
fi
done
rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true
pkill -HUP -f 'gtk' 2>/dev/null || true`;
Quickshell.execDetached(["sh", "-lc", configScript]);
@@ -1030,8 +1038,7 @@ Singleton {
fi
}
update_qt_icon_theme ${_configDir}/qt5ct/qt5ct.conf '${qtThemeNameEscaped}'
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'
rm -rf '${home}'/.cache/icon-cache '${home}'/.cache/thumbnails 2>/dev/null || true`;
update_qt_icon_theme ${_configDir}/qt6ct/qt6ct.conf '${qtThemeNameEscaped}'`;
Quickshell.execDetached(["sh", "-lc", script]);
}
@@ -1074,11 +1081,15 @@ Singleton {
_legacyWeatherLocation = obj.weatherLocation;
if (obj?.weatherCoordinates !== undefined)
_legacyWeatherCoordinates = obj.weatherCoordinates;
if (obj?.vpnLastConnected !== undefined && obj.vpnLastConnected !== "") {
_legacyVpnLastConnected = obj.vpnLastConnected;
SessionData.vpnLastConnected = _legacyVpnLastConnected;
SessionData.saveSettings();
}
_loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
_hasLoaded = true;
applyStoredTheme();
applyStoredIconTheme();
updateCompositorCursor();
Processes.detectQtTools();
@@ -1089,7 +1100,6 @@ Singleton {
console.error("SettingsData: Failed to parse settings.json - file will not be overwritten. Error:", msg);
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse settings.json"), msg));
applyStoredTheme();
applyStoredIconTheme();
} finally {
_loading = false;
}
@@ -1251,11 +1261,11 @@ Singleton {
}
function getEffectiveTimeFormat() {
if (use24HourClock) {
if (use24HourClock)
return showSeconds ? "hh:mm:ss" : "hh:mm";
} else {
return showSeconds ? "h:mm:ss AP" : "h:mm AP";
}
if (padHours12Hour)
return showSeconds ? "hh:mm:ss AP" : "hh:mm AP";
return showSeconds ? "h:mm:ss AP" : "h:mm AP";
}
function getEffectiveClockDateFormat() {
@@ -2307,11 +2317,15 @@ Singleton {
_legacyWeatherLocation = obj.weatherLocation;
if (obj.weatherCoordinates !== undefined)
_legacyWeatherCoordinates = obj.weatherCoordinates;
if (obj.vpnLastConnected !== undefined && obj.vpnLastConnected !== "") {
_legacyVpnLastConnected = obj.vpnLastConnected;
SessionData.vpnLastConnected = _legacyVpnLastConnected;
SessionData.saveSettings();
}
_loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
_hasLoaded = true;
applyStoredTheme();
applyStoredIconTheme();
updateCompositorCursor();
} catch (e) {
_parseError = true;

View File

@@ -94,6 +94,9 @@ Singleton {
property var matugenColors: ({})
property var _pendingGenerateParams: null
property bool themeModeAutomationActive: false
property bool dmsServiceWasDisconnected: true
readonly property var dank16: {
const raw = matugenColors?.dank16;
if (!raw)
@@ -176,6 +179,227 @@ Singleton {
if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) {
switchTheme(SettingsData.currentThemeName, false, false);
}
if (typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled) {
startThemeModeAutomation();
}
}
Connections {
target: SessionData
enabled: typeof SessionData !== "undefined"
function onThemeModeAutoEnabledChanged() {
if (SessionData.themeModeAutoEnabled) {
root.startThemeModeAutomation();
} else {
root.stopThemeModeAutomation();
}
}
function onThemeModeAutoModeChanged() {
if (root.themeModeAutomationActive) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
root.syncLocationThemeSchedule();
}
}
function onThemeModeStartHourChanged() {
if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onThemeModeStartMinuteChanged() {
if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onThemeModeEndHourChanged() {
if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onThemeModeEndMinuteChanged() {
if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onThemeModeShareGammaSettingsChanged() {
if (root.themeModeAutomationActive) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
root.syncLocationThemeSchedule();
}
}
function onNightModeStartHourChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onNightModeStartMinuteChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onNightModeEndHourChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onNightModeEndMinuteChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) {
root.evaluateThemeMode();
root.syncTimeThemeSchedule();
}
}
function onLatitudeChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") {
if (!SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0 && typeof DMSService !== "undefined") {
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
});
}
root.evaluateThemeMode();
root.syncLocationThemeSchedule();
}
}
function onLongitudeChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") {
if (!SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0 && typeof DMSService !== "undefined") {
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
});
}
root.evaluateThemeMode();
root.syncLocationThemeSchedule();
}
}
function onNightModeUseIPLocationChanged() {
if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") {
if (typeof DMSService !== "undefined") {
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {
"use": SessionData.nightModeUseIPLocation
}, response => {
if (!response.error && !SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
});
}
});
}
root.evaluateThemeMode();
root.syncLocationThemeSchedule();
}
}
}
// React to gamma backend's isDay state changes for location-based mode
Connections {
target: DisplayService
enabled: typeof DisplayService !== "undefined" && typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "location" && !themeAutoBackendAvailable()
function onGammaIsDayChanged() {
if (root.isLightMode !== DisplayService.gammaIsDay) {
root.setLightMode(DisplayService.gammaIsDay, true, true);
}
}
}
Connections {
target: DMSService
enabled: typeof DMSService !== "undefined" && typeof SessionData !== "undefined"
function onLoginctlEvent(event) {
if (!SessionData.themeModeAutoEnabled)
return;
if (event.event === "unlock" || event.event === "resume") {
if (!themeAutoBackendAvailable()) {
root.evaluateThemeMode();
return;
}
DMSService.sendRequest("theme.auto.trigger", {});
}
}
function onThemeAutoStateUpdate(data) {
if (!SessionData.themeModeAutoEnabled) {
return;
}
applyThemeAutoState(data);
}
function onConnectionStateChanged() {
if (DMSService.isConnected && SessionData.themeModeAutoMode === "time") {
root.syncTimeThemeSchedule();
}
if (DMSService.isConnected && SessionData.themeModeAutoMode === "location") {
root.syncLocationThemeSchedule();
}
if (themeAutoBackendAvailable() && SessionData.themeModeAutoEnabled) {
DMSService.sendRequest("theme.auto.getState", null, response => {
if (response && response.result) {
applyThemeAutoState(response.result);
}
});
}
if (!SessionData.themeModeAutoEnabled) {
return;
}
if (DMSService.isConnected && SessionData.themeModeAutoMode === "location") {
if (SessionData.nightModeUseIPLocation) {
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {
"use": true
}, response => {
if (!response.error) {
console.info("Theme automation: IP location enabled after connection");
}
});
} else if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {
"use": false
}, response => {
if (!response.error) {
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
}, locationResponse => {
if (locationResponse?.error) {
console.warn("Theme automation: Failed to set location", locationResponse.error);
}
});
}
});
} else {
console.warn("Theme automation: No location configured");
}
}
}
}
function applyGreeterTheme(themeName) {
@@ -330,6 +554,58 @@ Singleton {
property color errorHover: Qt.rgba(error.r, error.g, error.b, 0.12)
property color errorPressed: Qt.rgba(error.r, error.g, error.b, 0.16)
readonly property color ccTileActiveBg: {
switch (SettingsData.controlCenterTileColorMode) {
case "primaryContainer":
return primaryContainer;
case "secondary":
return secondary;
case "surfaceVariant":
return surfaceVariant;
default:
return primary;
}
}
readonly property color ccTileActiveText: {
switch (SettingsData.controlCenterTileColorMode) {
case "primaryContainer":
return primary;
case "secondary":
return surfaceText;
case "surfaceVariant":
return surfaceText;
default:
return primaryText;
}
}
readonly property color ccTileInactiveIcon: {
switch (SettingsData.controlCenterTileColorMode) {
case "primaryContainer":
return primary;
case "secondary":
return secondary;
case "surfaceVariant":
return surfaceText;
default:
return primary;
}
}
readonly property color ccTileRing: {
switch (SettingsData.controlCenterTileColorMode) {
case "primaryContainer":
return Qt.rgba(primary.r, primary.g, primary.b, 0.22);
case "secondary":
return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.22);
case "surfaceVariant":
return Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.22);
default:
return Qt.rgba(primaryText.r, primaryText.g, primaryText.b, 0.22);
}
}
property color shadowMedium: Qt.rgba(0, 0, 0, 0.08)
property color shadowStrong: Qt.rgba(0, 0, 0, 0.3)
@@ -491,7 +767,9 @@ Singleton {
property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 1.0
function screenTransition() {
CompositorService.isNiri && NiriService.doScreenTransition();
if (CompositorService.isNiri) {
NiriService.doScreenTransition();
}
}
function switchTheme(themeName, savePrefs = true, enableTransition = true) {
@@ -543,8 +821,10 @@ Singleton {
}
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode)
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode) {
SessionData.setLightMode(light);
}
if (!isGreeterMode) {
// Skip with matugen because, our script runner will do it.
if (!matugenAvailable) {
@@ -948,7 +1228,7 @@ Singleton {
skipTemplates.push("kcolorscheme");
if (!SettingsData.matugenTemplateVscode)
skipTemplates.push("vscode");
if (!SettingsData.matugenTemplateEmacs)
if (!SettingsData.matugenTemplateEmacs)
skipTemplates.push("emacs");
}
if (skipTemplates.length > 0) {
@@ -1233,7 +1513,7 @@ Singleton {
return `#${invR}${invG}${invB}`;
}
property string baseLogoColor: {
property var baseLogoColor: {
if (typeof SettingsData === "undefined")
return "";
const colorOverride = SettingsData.launcherLogoColorOverride;
@@ -1246,7 +1526,7 @@ Singleton {
return colorOverride;
}
property string effectiveLogoColor: {
property var effectiveLogoColor: {
if (typeof SettingsData === "undefined")
return "";
@@ -1453,4 +1733,303 @@ Singleton {
root.switchTheme(defaultTheme, true, false);
}
}
// Theme mode automation functions
function themeAutoBackendAvailable() {
return typeof DMSService !== "undefined" && DMSService.isConnected && Array.isArray(DMSService.capabilities) && DMSService.capabilities.includes("theme.auto");
}
function applyThemeAutoState(state) {
if (!state) {
return;
}
if (state.config && state.config.mode && state.config.mode !== SessionData.themeModeAutoMode) {
return;
}
if (typeof SessionData !== "undefined" && state.nextTransition !== undefined) {
SessionData.themeModeNextTransition = state.nextTransition || "";
}
if (state.isLight !== undefined && root.isLightMode !== state.isLight) {
root.setLightMode(state.isLight, true, true);
}
}
function syncTimeThemeSchedule() {
if (typeof SessionData === "undefined" || typeof DMSService === "undefined") {
return;
}
if (!DMSService.isConnected) {
return;
}
const timeModeActive = SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "time";
if (!timeModeActive) {
return;
}
DMSService.sendRequest("theme.auto.setMode", {
"mode": "time"
});
const shareSettings = SessionData.themeModeShareGammaSettings;
const startHour = shareSettings ? SessionData.nightModeStartHour : SessionData.themeModeStartHour;
const startMinute = shareSettings ? SessionData.nightModeStartMinute : SessionData.themeModeStartMinute;
const endHour = shareSettings ? SessionData.nightModeEndHour : SessionData.themeModeEndHour;
const endMinute = shareSettings ? SessionData.nightModeEndMinute : SessionData.themeModeEndMinute;
DMSService.sendRequest("theme.auto.setSchedule", {
"startHour": startHour,
"startMinute": startMinute,
"endHour": endHour,
"endMinute": endMinute
}, response => {
if (response && response.error) {
console.error("Theme automation: Failed to sync time schedule:", response.error);
}
});
DMSService.sendRequest("theme.auto.setEnabled", {
"enabled": true
});
DMSService.sendRequest("theme.auto.trigger", {});
}
function syncLocationThemeSchedule() {
if (typeof SessionData === "undefined" || typeof DMSService === "undefined") {
return;
}
if (!DMSService.isConnected) {
return;
}
const locationModeActive = SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "location";
if (!locationModeActive) {
return;
}
DMSService.sendRequest("theme.auto.setMode", {
"mode": "location"
});
if (SessionData.nightModeUseIPLocation) {
DMSService.sendRequest("theme.auto.setUseIPLocation", {
"use": true
});
} else {
DMSService.sendRequest("theme.auto.setUseIPLocation", {
"use": false
});
if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
DMSService.sendRequest("theme.auto.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
});
}
}
DMSService.sendRequest("theme.auto.setEnabled", {
"enabled": true
});
DMSService.sendRequest("theme.auto.trigger", {});
}
function evaluateThemeMode() {
if (typeof SessionData === "undefined" || !SessionData.themeModeAutoEnabled) {
return;
}
if (themeAutoBackendAvailable()) {
DMSService.sendRequest("theme.auto.getState", null, response => {
if (response && response.result) {
applyThemeAutoState(response.result);
}
});
return;
}
const mode = SessionData.themeModeAutoMode;
if (mode === "location") {
evaluateLocationBasedThemeMode();
} else {
evaluateTimeBasedThemeMode();
}
}
function evaluateLocationBasedThemeMode() {
if (typeof DisplayService !== "undefined") {
const shouldBeLight = DisplayService.gammaIsDay;
if (root.isLightMode !== shouldBeLight) {
root.setLightMode(shouldBeLight, true, true);
}
return;
}
if (!SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
const shouldBeLight = calculateIsDaytime(SessionData.latitude, SessionData.longitude);
if (root.isLightMode !== shouldBeLight) {
root.setLightMode(shouldBeLight, true, true);
}
return;
}
if (root.themeModeAutomationActive) {
if (SessionData.nightModeUseIPLocation) {
console.warn("Theme automation: Waiting for IP location from backend");
} else {
console.warn("Theme automation: Location mode requires coordinates");
}
}
}
function evaluateTimeBasedThemeMode() {
const shareSettings = SessionData.themeModeShareGammaSettings;
const startHour = shareSettings ? SessionData.nightModeStartHour : SessionData.themeModeStartHour;
const startMinute = shareSettings ? SessionData.nightModeStartMinute : SessionData.themeModeStartMinute;
const endHour = shareSettings ? SessionData.nightModeEndHour : SessionData.themeModeEndHour;
const endMinute = shareSettings ? SessionData.nightModeEndMinute : SessionData.themeModeEndMinute;
const now = new Date();
const currentMinutes = now.getHours() * 60 + now.getMinutes();
const startMinutes = startHour * 60 + startMinute;
const endMinutes = endHour * 60 + endMinute;
let shouldBeLight;
if (startMinutes < endMinutes) {
shouldBeLight = currentMinutes < startMinutes || currentMinutes >= endMinutes;
} else {
shouldBeLight = currentMinutes >= endMinutes && currentMinutes < startMinutes;
}
if (root.isLightMode !== shouldBeLight) {
root.setLightMode(shouldBeLight, true, true);
}
}
function calculateIsDaytime(lat, lng) {
const now = new Date();
const start = new Date(now.getFullYear(), 0, 0);
const diff = now - start;
const dayOfYear = Math.floor(diff / 86400000);
const latRad = lat * Math.PI / 180;
const declination = 23.45 * Math.sin((360 / 365) * (dayOfYear - 81) * Math.PI / 180);
const declinationRad = declination * Math.PI / 180;
const cosHourAngle = -Math.tan(latRad) * Math.tan(declinationRad);
if (cosHourAngle > 1) {
return false; // Polar night
}
if (cosHourAngle < -1) {
return true; // Midnight sun
}
const hourAngle = Math.acos(cosHourAngle);
const hourAngleDeg = hourAngle * 180 / Math.PI;
const sunriseHour = 12 - hourAngleDeg / 15;
const sunsetHour = 12 + hourAngleDeg / 15;
const timeZoneOffset = now.getTimezoneOffset() / 60;
const localSunrise = sunriseHour - lng / 15 - timeZoneOffset;
const localSunset = sunsetHour - lng / 15 - timeZoneOffset;
const currentHour = now.getHours() + now.getMinutes() / 60;
const normalizeSunrise = ((localSunrise % 24) + 24) % 24;
const normalizeSunset = ((localSunset % 24) + 24) % 24;
return currentHour >= normalizeSunrise && currentHour < normalizeSunset;
}
// Helper function to send location to backend
function sendLocationToBackend() {
if (typeof SessionData === "undefined" || typeof DMSService === "undefined") {
return false;
}
if (!DMSService.isConnected) {
return false;
}
if (SessionData.nightModeUseIPLocation) {
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {
"use": true
}, response => {
if (response?.error) {
console.warn("Theme automation: Failed to enable IP location", response.error);
}
});
return true;
} else if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {
"use": false
}, response => {
if (!response.error) {
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
}, locResp => {
if (locResp?.error) {
console.warn("Theme automation: Failed to set location", locResp.error);
}
});
}
});
return true;
}
return false;
}
Timer {
id: locationRetryTimer
interval: 1000
repeat: true
running: false
property int retryCount: 0
onTriggered: {
if (root.sendLocationToBackend()) {
stop();
retryCount = 0;
root.evaluateThemeMode();
} else {
retryCount++;
if (retryCount >= 10) {
stop();
retryCount = 0;
}
}
}
}
function startThemeModeAutomation() {
root.themeModeAutomationActive = true;
root.syncTimeThemeSchedule();
root.syncLocationThemeSchedule();
const sent = root.sendLocationToBackend();
if (!sent && typeof SessionData !== "undefined" && SessionData.themeModeAutoMode === "location") {
locationRetryTimer.start();
} else {
root.evaluateThemeMode();
}
}
function stopThemeModeAutomation() {
root.themeModeAutomationActive = false;
if (typeof DMSService !== "undefined" && DMSService.isConnected) {
DMSService.sendRequest("theme.auto.setEnabled", {
"enabled": false
});
}
}
}

View File

@@ -35,10 +35,19 @@ var SPEC = {
nightModeUseIPLocation: { def: false },
nightModeLocationProvider: { def: "" },
themeModeAutoEnabled: { def: false },
themeModeAutoMode: { def: "time" },
themeModeStartHour: { def: 18 },
themeModeStartMinute: { def: 0 },
themeModeEndHour: { def: 6 },
themeModeEndMinute: { def: 0 },
themeModeShareGammaSettings: { def: true },
weatherLocation: { def: "New York, NY" },
weatherCoordinates: { def: "40.7128,-74.0060" },
pinnedApps: { def: [] },
barPinnedApps: { def: [] },
dockLauncherPosition: { def: 0 },
hiddenTrayIds: { def: [] },
recentColors: { def: [] },
@@ -60,7 +69,9 @@ var SPEC = {
hiddenApps: { def: [] },
appOverrides: { def: {} },
searchAppActions: { def: true }
searchAppActions: { def: true },
vpnLastConnected: { def: "" }
};
function getValidKeys() {

View File

@@ -1,6 +1,6 @@
.pragma library
.import "./SessionSpec.js" as SpecModule
.import "./SessionSpec.js" as SpecModule
function parse(root, jsonObj) {
var SPEC = SpecModule.SPEC;
@@ -68,6 +68,11 @@ function migrateToVersion(obj, targetVersion, settingsData) {
session.configVersion = 2;
}
if (currentVersion < 3) {
console.info("SessionData: Migrating session to version 3");
session.configVersion = 3;
}
return session;
}

View File

@@ -19,6 +19,7 @@ var SPEC = {
widgetBackgroundColor: { def: "sch" },
widgetColorMode: { def: "default" },
controlCenterTileColorMode: { def: "primary" },
cornerRadius: { def: 12, onChange: "updateCompositorLayout" },
niriLayoutGapsOverride: { def: -1, onChange: "updateCompositorLayout" },
niriLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" },
@@ -32,6 +33,7 @@ var SPEC = {
use24HourClock: { def: true },
showSeconds: { def: false },
padHours12Hour: { def: false },
useFahrenheit: { def: false },
windSpeedUnit: { def: "kmh" },
nightModeEnabled: { def: false },
@@ -78,16 +80,18 @@ var SPEC = {
privacyShowCameraIcon: { def: false },
privacyShowScreenShareIcon: { def: false },
controlCenterWidgets: { def: [
{ id: "volumeSlider", enabled: true, width: 50 },
{ id: "brightnessSlider", enabled: true, width: 50 },
{ id: "wifi", enabled: true, width: 50 },
{ id: "bluetooth", enabled: true, width: 50 },
{ id: "audioOutput", enabled: true, width: 50 },
{ id: "audioInput", enabled: true, width: 50 },
{ id: "nightMode", enabled: true, width: 50 },
{ id: "darkMode", enabled: true, width: 50 }
]},
controlCenterWidgets: {
def: [
{ id: "volumeSlider", enabled: true, width: 50 },
{ id: "brightnessSlider", enabled: true, width: 50 },
{ id: "wifi", enabled: true, width: 50 },
{ id: "bluetooth", enabled: true, width: 50 },
{ id: "audioOutput", enabled: true, width: 50 },
{ id: "audioInput", enabled: true, width: 50 },
{ id: "nightMode", enabled: true, width: 50 },
{ id: "darkMode", enabled: true, width: 50 }
]
},
showWorkspaceIndex: { def: false },
showWorkspaceName: { def: false },
@@ -95,6 +99,7 @@ var SPEC = {
workspaceScrolling: { def: false },
showWorkspaceApps: { def: false },
maxWorkspaceIcons: { def: 3 },
workspaceAppIconSizeOffset: { def: 0 },
groupWorkspaceApps: { def: true },
workspaceFollowFocus: { def: false },
showOccupiedWorkspacesOnly: { def: false },
@@ -118,13 +123,15 @@ var SPEC = {
keyboardLayoutNameCompactMode: { def: false },
runningAppsCurrentWorkspace: { def: false },
runningAppsGroupByApp: { def: false },
appIdSubstitutions: { def: [
{ pattern: "Spotify", replacement: "spotify", type: "exact" },
{ pattern: "beepertexts", replacement: "beeper", type: "exact" },
{ pattern: "home assistant desktop", replacement: "homeassistant-desktop", type: "exact" },
{ pattern: "com.transmissionbt.transmission", replacement: "transmission-gtk", type: "contains" },
{ pattern: "^steam_app_(\\d+)$", replacement: "steam_icon_$1", type: "regex" }
]},
appIdSubstitutions: {
def: [
{ pattern: "Spotify", replacement: "spotify", type: "exact" },
{ pattern: "beepertexts", replacement: "beeper", type: "exact" },
{ pattern: "home assistant desktop", replacement: "homeassistant-desktop", type: "exact" },
{ pattern: "com.transmissionbt.transmission", replacement: "transmission-gtk", type: "contains" },
{ pattern: "^steam_app_(\\d+)$", replacement: "steam_icon_$1", type: "regex" }
]
},
centeringMode: { def: "index" },
clockDateFormat: { def: "" },
lockDateFormat: { def: "" },
@@ -132,6 +139,10 @@ var SPEC = {
appLauncherViewMode: { def: "list" },
spotlightModalViewMode: { def: "list" },
browserPickerViewMode: { def: "grid" },
browserUsageHistory: { def: {} },
appPickerViewMode: { def: "grid" },
filePickerUsageHistory: { def: {} },
sortAppsAlphabetically: { def: false },
appLauncherGridColumns: { def: 4 },
spotlightCloseNiriOverview: { def: true },
@@ -148,7 +159,6 @@ var SPEC = {
weatherEnabled: { def: true },
networkPreference: { def: "auto" },
vpnLastConnected: { def: "" },
iconTheme: { def: "System Default", onChange: "applyStoredIconTheme" },
availableIconThemes: { def: ["System Default"], persist: false },
@@ -262,6 +272,9 @@ var SPEC = {
dockLauncherLogoSizeOffset: { def: 0 },
dockLauncherLogoBrightness: { def: 0.5, coerce: percentToUnit },
dockLauncherLogoContrast: { def: 1, coerce: percentToUnit },
dockMaxVisibleApps: { def: 0 },
dockMaxVisibleRunningApps: { def: 0 },
dockShowOverflowBadge: { def: true },
notificationOverlayEnabled: { def: false },
overviewRows: { def: 2, persist: false },
@@ -276,6 +289,7 @@ var SPEC = {
lockScreenShowDate: { def: true },
lockScreenShowProfileImage: { def: true },
lockScreenShowPasswordField: { def: true },
lockScreenShowMediaPlayer: { def: true },
lockScreenPowerOffMonitorsOnLock: { def: false },
enableFprint: { def: false },
maxFprintTries: { def: 15 },
@@ -300,7 +314,7 @@ var SPEC = {
osdAlwaysShowValue: { def: false },
osdPosition: { def: 5 },
osdVolumeEnabled: { def: true },
osdMediaVolumeEnabled : { def: true },
osdMediaVolumeEnabled: { def: true },
osdBrightnessEnabled: { def: true },
osdIdleInhibitorEnabled: { def: true },
osdMicMuteEnabled: { def: true },
@@ -331,52 +345,54 @@ var SPEC = {
niriOutputSettings: { def: {} },
hyprlandOutputSettings: { def: {} },
barConfigs: { def: [{
id: "default",
name: "Main Bar",
enabled: true,
position: 0,
screenPreferences: ["all"],
showOnLastDisplay: true,
leftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"],
centerWidgets: ["music", "clock", "weather"],
rightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
spacing: 4,
innerPadding: 4,
bottomGap: 0,
transparency: 1.0,
widgetTransparency: 1.0,
squareCorners: false,
noBackground: false,
gothCornersEnabled: false,
gothCornerRadiusOverride: false,
gothCornerRadiusValue: 12,
borderEnabled: false,
borderColor: "surfaceText",
borderOpacity: 1.0,
borderThickness: 1,
widgetOutlineEnabled: false,
widgetOutlineColor: "primary",
widgetOutlineOpacity: 1.0,
widgetOutlineThickness: 1,
fontScale: 1.0,
autoHide: false,
autoHideDelay: 250,
showOnWindowsOpen: false,
openOnOverview: false,
visible: true,
popupGapsAuto: true,
popupGapsManual: 4,
maximizeDetection: true,
scrollEnabled: true,
scrollXBehavior: "column",
scrollYBehavior: "workspace",
shadowIntensity: 0,
shadowOpacity: 60,
shadowColorMode: "text",
shadowCustomColor: "#000000",
clickThrough: false
}], onChange: "updateBarConfigs" },
barConfigs: {
def: [{
id: "default",
name: "Main Bar",
enabled: true,
position: 0,
screenPreferences: ["all"],
showOnLastDisplay: true,
leftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"],
centerWidgets: ["music", "clock", "weather"],
rightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
spacing: 4,
innerPadding: 4,
bottomGap: 0,
transparency: 1.0,
widgetTransparency: 1.0,
squareCorners: false,
noBackground: false,
gothCornersEnabled: false,
gothCornerRadiusOverride: false,
gothCornerRadiusValue: 12,
borderEnabled: false,
borderColor: "surfaceText",
borderOpacity: 1.0,
borderThickness: 1,
widgetOutlineEnabled: false,
widgetOutlineColor: "primary",
widgetOutlineOpacity: 1.0,
widgetOutlineThickness: 1,
fontScale: 1.0,
autoHide: false,
autoHideDelay: 250,
showOnWindowsOpen: false,
openOnOverview: false,
visible: true,
popupGapsAuto: true,
popupGapsManual: 4,
maximizeDetection: true,
scrollEnabled: true,
scrollXBehavior: "column",
scrollYBehavior: "workspace",
shadowIntensity: 0,
shadowOpacity: 60,
shadowColorMode: "text",
shadowCustomColor: "#000000",
clickThrough: false
}], onChange: "updateBarConfigs"
},
desktopClockEnabled: { def: false },
desktopClockStyle: { def: "analog" },
@@ -431,7 +447,7 @@ var SPEC = {
};
function getValidKeys() {
return Object.keys(SPEC).filter(function(k) { return SPEC[k].persist !== false; }).concat(["configVersion"]);
return Object.keys(SPEC).filter(function (k) { return SPEC[k].persist !== false; }).concat(["configVersion"]);
}
function set(root, key, value, saveFn, hooks) {

View File

@@ -1,6 +1,6 @@
.pragma library
.import "./SettingsSpec.js" as SpecModule
.import "./SettingsSpec.js" as SpecModule
function parse(root, jsonObj) {
var SPEC = SpecModule.SPEC;

View File

@@ -550,6 +550,11 @@ Item {
AppPickerModal {
id: filePickerModal
title: I18n.tr("Open with...")
viewMode: SettingsData.appPickerViewMode || "grid"
onViewModeChanged: {
SettingsData.set("appPickerViewMode", viewMode)
}
function shellEscape(str) {
return "'" + str.replace(/'/g, "'\\''") + "'";
@@ -644,6 +649,18 @@ Item {
}
}
LazyLoader {
id: workspaceRenameModalLoader
active: false
Component.onCompleted: PopoutService.workspaceRenameModalLoader = workspaceRenameModalLoader
WorkspaceRenameModal {
id: workspaceRenameModal
}
}
LazyLoader {
id: processListModalLoader
@@ -769,6 +786,7 @@ Item {
hyprKeybindsModalLoader: hyprKeybindsModalLoader
dankBarRepeater: dankBarRepeater
hyprlandOverviewLoader: hyprlandOverviewLoader
workspaceRenameModalLoader: workspaceRenameModalLoader
}
Variants {

View File

@@ -15,6 +15,7 @@ Item {
required property var hyprKeybindsModalLoader
required property var dankBarRepeater
required property var hyprlandOverviewLoader
required property var workspaceRenameModalLoader
function getFirstBar() {
if (!root.dankBarRepeater || root.dankBarRepeater.count === 0)
@@ -1062,7 +1063,7 @@ Item {
}
function toggleQuery(query: string): string {
PopoutService.toggleDankLauncherV2();
PopoutService.toggleDankLauncherV2WithQuery(query);
return "LAUNCHER_TOGGLE_QUERY_SUCCESS";
}
@@ -1106,13 +1107,86 @@ Item {
}
function toggleQuery(query: string): string {
PopoutService.toggleDankLauncherV2();
PopoutService.toggleDankLauncherV2WithQuery(query);
return "SPOTLIGHT_TOGGLE_QUERY_SUCCESS";
}
target: "spotlight"
}
IpcHandler {
function info(message: string): string {
if (!message)
return "ERROR: No message specified";
ToastService.showInfo(message);
return "TOAST_INFO_SUCCESS";
}
function infoWith(message: string, details: string, command: string, category: string): string {
if (!message)
return "ERROR: No message specified";
ToastService.showInfo(message, details, command, category);
return "TOAST_INFO_SUCCESS";
}
function warn(message: string): string {
if (!message)
return "ERROR: No message specified";
ToastService.showWarning(message);
return "TOAST_WARN_SUCCESS";
}
function warnWith(message: string, details: string, command: string, category: string): string {
if (!message)
return "ERROR: No message specified";
ToastService.showWarning(message, details, command, category);
return "TOAST_WARN_SUCCESS";
}
function error(message: string): string {
if (!message)
return "ERROR: No message specified";
ToastService.showError(message);
return "TOAST_ERROR_SUCCESS";
}
function errorWith(message: string, details: string, command: string, category: string): string {
if (!message)
return "ERROR: No message specified";
ToastService.showError(message, details, command, category);
return "TOAST_ERROR_SUCCESS";
}
function hide(): string {
ToastService.hideToast();
return "TOAST_HIDE_SUCCESS";
}
function dismiss(category: string): string {
if (!category)
return "ERROR: No category specified";
ToastService.dismissCategory(category);
return "TOAST_DISMISS_SUCCESS";
}
function status(): string {
if (!ToastService.toastVisible)
return "hidden";
const levels = ["info", "warn", "error"];
return `visible:${levels[ToastService.currentLevel]}:${ToastService.currentMessage}`;
}
target: "toast"
}
IpcHandler {
function open(): string {
FirstLaunchService.showWelcome();
@@ -1292,4 +1366,40 @@ Item {
target: "desktopWidget"
}
IpcHandler {
function open(): string {
root.workspaceRenameModalLoader.active = true;
if (root.workspaceRenameModalLoader.item) {
const ws = NiriService.workspaces[NiriService.focusedWorkspaceId];
root.workspaceRenameModalLoader.item.show(ws?.name || "");
return "WORKSPACE_RENAME_MODAL_OPENED";
}
return "WORKSPACE_RENAME_MODAL_NOT_FOUND";
}
function close(): string {
if (root.workspaceRenameModalLoader.item) {
root.workspaceRenameModalLoader.item.hide();
return "WORKSPACE_RENAME_MODAL_CLOSED";
}
return "WORKSPACE_RENAME_MODAL_NOT_FOUND";
}
function toggle(): string {
root.workspaceRenameModalLoader.active = true;
if (root.workspaceRenameModalLoader.item) {
if (root.workspaceRenameModalLoader.item.visible) {
root.workspaceRenameModalLoader.item.hide();
return "WORKSPACE_RENAME_MODAL_CLOSED";
}
const ws = NiriService.workspaces[NiriService.focusedWorkspaceId];
root.workspaceRenameModalLoader.item.show(ws?.name || "");
return "WORKSPACE_RENAME_MODAL_OPENED";
}
return "WORKSPACE_RENAME_MODAL_NOT_FOUND";
}
target: "workspace-rename"
}
}

View File

@@ -29,16 +29,7 @@ DankModal {
property int activeImageLoads: 0
readonly property int maxConcurrentLoads: 3
readonly property bool clipboardAvailable: DMSService.isConnected && (DMSService.capabilities.length === 0 || DMSService.capabilities.includes("clipboard"))
property bool wtypeAvailable: false
Process {
id: wtypeCheck
command: ["which", "wtype"]
running: true
onExited: exitCode => {
clipboardHistoryModal.wtypeAvailable = (exitCode === 0);
}
}
readonly property bool wtypeAvailable: SessionService.wtypeAvailable
Process {
id: wtypeProcess

View File

@@ -33,7 +33,8 @@ Rectangle {
result.push(selectedItem.primaryAction);
}
if (selectedItem?.type === "plugin") {
switch (selectedItem?.type) {
case "plugin":
var pluginActions = getPluginContextMenuActions();
for (var i = 0; i < pluginActions.length; i++) {
var act = pluginActions[i];
@@ -44,24 +45,45 @@ Rectangle {
pluginAction: act.action
});
}
} else if (selectedItem?.type === "app" && !selectedItem?.isCore) {
break;
case "plugin_browse":
if (selectedItem?.actions) {
for (var i = 0; i < selectedItem.actions.length; i++) {
result.push(selectedItem.actions[i]);
}
}
break;
case "app":
if (selectedItem?.isCore)
break;
if (selectedItem?.actions) {
for (var i = 0; i < selectedItem.actions.length; i++) {
result.push(selectedItem.actions[i]);
}
}
if (SessionService.nvidiaCommand) {
result.push({
name: I18n.tr("Launch on dGPU"),
icon: "memory",
action: "launch_dgpu"
});
}
break;
}
return result;
}
readonly property bool hasActions: {
if (selectedItem?.type === "app" && !selectedItem?.isCore)
return true;
if (selectedItem?.type === "plugin") {
var pluginActions = getPluginContextMenuActions();
return pluginActions.length > 0;
switch (selectedItem?.type) {
case "app":
return !selectedItem?.isCore;
case "plugin":
return getPluginContextMenuActions().length > 0;
case "plugin_browse":
return selectedItem?.actions?.length > 0;
default:
return actions.length > 1;
}
return actions.length > 1;
}
width: parent?.width ?? 200

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,157 @@
.pragma library
function getFileIcon(filename) {
var ext = filename.lastIndexOf(".") > 0 ? filename.substring(filename.lastIndexOf(".") + 1).toLowerCase() : "";
switch (ext) {
case "pdf":
return "picture_as_pdf";
case "doc":
case "docx":
case "odt":
return "description";
case "xls":
case "xlsx":
case "ods":
return "table_chart";
case "ppt":
case "pptx":
case "odp":
return "slideshow";
case "txt":
case "md":
case "rst":
return "article";
case "jpg":
case "jpeg":
case "png":
case "gif":
case "svg":
case "webp":
return "image";
case "mp3":
case "wav":
case "flac":
case "ogg":
return "audio_file";
case "mp4":
case "mkv":
case "avi":
case "webm":
return "video_file";
case "zip":
case "tar":
case "gz":
case "7z":
case "rar":
return "folder_zip";
case "js":
case "ts":
case "py":
case "rs":
case "go":
case "java":
case "c":
case "cpp":
case "h":
return "code";
case "html":
case "css":
case "htm":
return "web";
case "json":
case "xml":
case "yaml":
case "yml":
return "data_object";
case "sh":
case "bash":
case "zsh":
return "terminal";
default:
return "insert_drive_file";
}
}
function stripIconPrefix(iconName) {
if (!iconName)
return "extension";
if (iconName.startsWith("unicode:"))
return iconName.substring(8);
if (iconName.startsWith("material:"))
return iconName.substring(9);
if (iconName.startsWith("image:"))
return iconName.substring(6);
return iconName;
}
function detectIconType(iconName) {
if (!iconName)
return "material";
if (iconName.startsWith("unicode:"))
return "unicode";
if (iconName.startsWith("material:"))
return "material";
if (iconName.startsWith("image:"))
return "image";
if (iconName.indexOf("/") >= 0 || iconName.indexOf(".") >= 0)
return "image";
if (/^[a-z]+-[a-z]/.test(iconName.toLowerCase()))
return "image";
return "material";
}
function evaluateCalculator(query) {
if (!query || query.length === 0)
return null;
var mathExpr = query.replace(/[^0-9+\-*/().%\s^]/g, "");
if (mathExpr.length < 2)
return null;
var hasMath = /[+\-*/^%]/.test(query) && /\d/.test(query);
if (!hasMath)
return null;
try {
var sanitized = mathExpr.replace(/\^/g, "**");
var result = Function('"use strict"; return (' + sanitized + ')')();
if (typeof result === "number" && isFinite(result)) {
var displayResult = Number.isInteger(result) ? result.toString() : result.toFixed(6).replace(/\.?0+$/, "");
return {
expression: query,
result: result,
displayResult: displayResult
};
}
} catch (e) { }
return null;
}
function sortPluginIdsByOrder(pluginIds, order) {
if (!order || order.length === 0)
return pluginIds;
var orderMap = {};
for (var i = 0; i < order.length; i++)
orderMap[order[i]] = i;
return pluginIds.slice().sort(function (a, b) {
var aOrder = orderMap[a] !== undefined ? orderMap[a] : 9999;
var bOrder = orderMap[b] !== undefined ? orderMap[b] : 9999;
return aOrder - bOrder;
});
}
function sortPluginsOrdered(plugins, order) {
if (!order || order.length === 0)
return plugins;
var orderMap = {};
for (var i = 0; i < order.length; i++)
orderMap[order[i]] = i;
return plugins.sort(function (a, b) {
var aOrder = orderMap[a.id] !== undefined ? orderMap[a.id] : 9999;
var bOrder = orderMap[b.id] !== undefined ? orderMap[b.id] : 9999;
return aOrder - bOrder;
});
}

View File

@@ -186,6 +186,14 @@ Item {
}
}
function toggleWithQuery(query) {
if (spotlightOpen) {
hide();
} else {
showWithQuery(query);
}
}
Timer {
id: closeCleanupTimer
interval: Theme.expressiveDurations.expressiveFastSpatial + 50

View File

@@ -36,7 +36,7 @@ Rectangle {
readonly property int computedIconSize: Math.min(48, Math.max(32, width * 0.45))
radius: Theme.cornerRadius
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent"
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
Column {
anchors.centerIn: parent

View File

@@ -0,0 +1,223 @@
.pragma library
.import "ControllerUtils.js" as Utils
function transformApp(app, override, defaultActions, primaryActionLabel) {
var appId = app.id || app.execString || app.exec || "";
var actions = [];
if (app.actions && app.actions.length > 0) {
for (var i = 0; i < app.actions.length; i++) {
actions.push({
name: app.actions[i].name,
icon: "play_arrow",
actionData: app.actions[i]
});
}
}
return {
id: appId,
type: "app",
name: override?.name || app.name || "",
subtitle: override?.comment || app.comment || "",
icon: override?.icon || app.icon || "application-x-executable",
iconType: "image",
section: "apps",
data: app,
keywords: app.keywords || [],
actions: actions,
primaryAction: {
name: primaryActionLabel,
icon: "open_in_new",
action: "launch"
}
};
}
function transformCoreApp(app, openLabel) {
var iconName = "apps";
var iconType = "material";
if (app.icon) {
if (app.icon.startsWith("svg+corner:")) {
iconType = "composite";
} else if (app.icon.startsWith("material:")) {
iconName = app.icon.substring(9);
} else {
iconName = app.icon;
iconType = "image";
}
}
return {
id: app.builtInPluginId || app.action || "",
type: "app",
name: app.name || "",
subtitle: app.comment || "",
icon: iconName,
iconType: iconType,
iconFull: app.icon,
section: "apps",
data: app,
isCore: true,
actions: [],
primaryAction: {
name: openLabel,
icon: "open_in_new",
action: "launch"
}
};
}
function transformBuiltInLauncherItem(item, pluginId, openLabel) {
var rawIcon = item.icon || "extension";
var icon = Utils.stripIconPrefix(rawIcon);
var iconType = item.iconType;
if (!iconType) {
if (rawIcon.startsWith("material:"))
iconType = "material";
else if (rawIcon.startsWith("unicode:"))
iconType = "unicode";
else
iconType = "image";
}
return {
id: item.action || "",
type: "plugin",
name: item.name || "",
subtitle: item.comment || "",
icon: icon,
iconType: iconType,
section: "plugin_" + pluginId,
data: item,
pluginId: pluginId,
isBuiltInLauncher: true,
keywords: item.keywords || [],
actions: [],
primaryAction: {
name: openLabel,
icon: "open_in_new",
action: "execute"
}
};
}
function transformFileResult(file, openLabel, openFolderLabel, copyPathLabel) {
var filename = file.path ? file.path.split("/").pop() : "";
var dirname = file.path ? file.path.substring(0, file.path.lastIndexOf("/")) : "";
return {
id: file.path || "",
type: "file",
name: filename,
subtitle: dirname,
icon: Utils.getFileIcon(filename),
iconType: "material",
section: "files",
data: file,
actions: [
{
name: openFolderLabel,
icon: "folder_open",
action: "open_folder"
},
{
name: copyPathLabel,
icon: "content_copy",
action: "copy_path"
}
],
primaryAction: {
name: openLabel,
icon: "open_in_new",
action: "open"
}
};
}
function transformPluginItem(item, pluginId, selectLabel) {
var rawIcon = item.icon || "extension";
var icon = Utils.stripIconPrefix(rawIcon);
var iconType = item.iconType;
if (!iconType) {
if (rawIcon.startsWith("material:"))
iconType = "material";
else if (rawIcon.startsWith("unicode:"))
iconType = "unicode";
else
iconType = "image";
}
return {
id: item.id || item.name || "",
type: "plugin",
name: item.name || "",
subtitle: item.comment || item.description || "",
icon: icon,
iconType: iconType,
section: "plugin_" + pluginId,
data: item,
pluginId: pluginId,
keywords: item.keywords || [],
actions: item.actions || [],
primaryAction: item.primaryAction || {
name: selectLabel,
icon: "check",
action: "execute"
}
};
}
function createCalculatorItem(calc, query, copyLabel) {
return {
id: "calculator_result",
type: "calculator",
name: calc.displayResult,
subtitle: query + " =",
icon: "calculate",
iconType: "material",
section: "calculator",
data: {
expression: calc.expression,
result: calc.result
},
actions: [],
primaryAction: {
name: copyLabel,
icon: "content_copy",
action: "copy"
}
};
}
function createPluginBrowseItem(pluginId, plugin, trigger, isBuiltIn, isAllowed, browseLabel, triggerLabel, noTriggerLabel) {
var rawIcon = isBuiltIn ? (plugin.cornerIcon || "extension") : (plugin.icon || "extension");
return {
id: "browse_" + pluginId,
type: "plugin_browse",
name: plugin.name || pluginId,
subtitle: trigger ? triggerLabel.replace("%1", trigger) : noTriggerLabel,
icon: isBuiltIn ? rawIcon : Utils.stripIconPrefix(rawIcon),
iconType: isBuiltIn ? "material" : Utils.detectIconType(rawIcon),
section: "browse_plugins",
data: {
pluginId: pluginId,
plugin: plugin,
isBuiltIn: isBuiltIn
},
actions: [
{
name: "All",
icon: isAllowed ? "visibility" : "visibility_off",
action: "toggle_all_visibility"
}
],
primaryAction: {
name: browseLabel,
icon: "arrow_forward",
action: "browse_plugin"
}
};
}

View File

@@ -209,6 +209,10 @@ FocusScope {
return;
case Qt.Key_Return:
case Qt.Key_Enter:
if (event.modifiers & Qt.ShiftModifier) {
controller.pasteSelected();
return;
}
if (actionPanel.expanded && actionPanel.selectedActionIndex > 0) {
actionPanel.executeSelectedAction();
} else {
@@ -267,21 +271,32 @@ FocusScope {
anchors.fill: parent
visible: !editMode
Rectangle {
Item {
id: footerBar
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.leftMargin: root.parentModal?.borderWidth ?? 1
anchors.rightMargin: root.parentModal?.borderWidth ?? 1
anchors.bottomMargin: root.parentModal?.borderWidth ?? 1
readonly property bool showFooter: SettingsData.dankLauncherV2Size !== "micro" && SettingsData.dankLauncherV2ShowFooter
height: showFooter ? 32 : 0
height: showFooter ? 36 : 0
visible: showFooter
color: Theme.surfaceContainerHigh
radius: Theme.cornerRadius
clip: true
Rectangle {
anchors.fill: parent
anchors.topMargin: -Theme.cornerRadius
color: Theme.surfaceContainerHigh
radius: Theme.cornerRadius
}
Row {
id: modeButtonsRow
x: I18n.isRtl ? parent.width - width - Theme.spacingS : Theme.spacingS
y: (parent.height - height) / 2
anchors.left: parent.left
anchors.leftMargin: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
layoutDirection: I18n.isRtl ? Qt.RightToLeft : Qt.LeftToRight
spacing: 2
Repeater {
@@ -312,28 +327,25 @@ FocusScope {
required property var modelData
required property int index
width: modeButtonMetrics.width + 14 + Theme.spacingXS + Theme.spacingM * 2 + Theme.spacingS
height: footerBar.height - 4
radius: Theme.cornerRadius - 2
width: buttonContent.width + Theme.spacingM * 2
height: 28
radius: Theme.cornerRadius
color: controller.searchMode === modelData.id || modeArea.containsMouse ? Theme.primaryContainer : "transparent"
TextMetrics {
id: modeButtonMetrics
font.pixelSize: Theme.fontSizeSmall
text: modelData.label
}
Row {
id: buttonContent
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: modelData.icon
size: 14
color: controller.searchMode === modelData.id ? Theme.primary : Theme.surfaceVariantText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: modelData.label
font.pixelSize: Theme.fontSizeSmall
color: controller.searchMode === modelData.id ? Theme.primary : Theme.surfaceText
@@ -353,23 +365,28 @@ FocusScope {
Row {
id: hintsRow
x: I18n.isRtl ? Theme.spacingS : parent.width - width - Theme.spacingS
y: (parent.height - height) / 2
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
layoutDirection: I18n.isRtl ? Qt.RightToLeft : Qt.LeftToRight
spacing: Theme.spacingM
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: "↑↓ " + I18n.tr("nav")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: "↵ " + I18n.tr("open")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: "Tab " + I18n.tr("actions")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
@@ -386,7 +403,6 @@ FocusScope {
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingM
anchors.bottomMargin: Theme.spacingXS
spacing: Theme.spacingXS
clip: false
@@ -479,9 +495,64 @@ FocusScope {
}
}
Row {
id: categoryRow
width: parent.width
height: controller.activePluginCategories.length > 0 ? 36 : 0
visible: controller.activePluginCategories.length > 0
spacing: Theme.spacingS
clip: true
Behavior on height {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
DankDropdown {
id: categoryDropdown
width: Math.min(200, parent.width)
compactMode: true
dropdownWidth: 200
popupWidth: 240
maxPopupHeight: 300
enableFuzzySearch: controller.activePluginCategories.length > 8
currentValue: {
const cats = controller.activePluginCategories;
const current = controller.activePluginCategory;
if (!current)
return cats.length > 0 ? cats[0].name : "";
for (let i = 0; i < cats.length; i++) {
if (cats[i].id === current)
return cats[i].name;
}
return cats.length > 0 ? cats[0].name : "";
}
options: {
const cats = controller.activePluginCategories;
const names = [];
for (let i = 0; i < cats.length; i++)
names.push(cats[i].name);
return names;
}
onValueChanged: value => {
const cats = controller.activePluginCategories;
for (let i = 0; i < cats.length; i++) {
if (cats[i].name === value) {
controller.setActivePluginCategory(cats[i].id);
return;
}
}
}
}
}
Item {
width: parent.width
height: parent.height - searchField.height - actionPanel.height - Theme.spacingXS * 2
height: parent.height - searchField.height - categoryRow.height - actionPanel.height - Theme.spacingXS * (categoryRow.visible ? 3 : 2)
opacity: root.parentModal?.isClosing ? 0 : 1
ResultsList {

View File

@@ -131,6 +131,16 @@ Popup {
items.push({
type: "separator"
});
if (isRegularApp && SessionService.nvidiaCommand) {
items.push({
type: "item",
icon: "memory",
text: I18n.tr("Launch on dGPU"),
action: launchWithNvidia
});
}
items.push({
type: "item",
icon: "launch",

View File

@@ -0,0 +1,245 @@
.pragma library
function getFirstItemIndex(flatModel) {
for (var i = 0; i < flatModel.length; i++) {
if (!flatModel[i].isHeader)
return i;
}
return 0;
}
function findNextNonHeaderIndex(flatModel, startIndex) {
for (var i = startIndex; i < flatModel.length; i++) {
if (!flatModel[i].isHeader)
return i;
}
return -1;
}
function findPrevNonHeaderIndex(flatModel, startIndex) {
for (var i = startIndex; i >= 0; i--) {
if (!flatModel[i].isHeader)
return i;
}
return -1;
}
function getSectionBounds(flatModel, sectionId) {
var start = -1, end = -1;
for (var i = 0; i < flatModel.length; i++) {
if (flatModel[i].isHeader && flatModel[i].section?.id === sectionId) {
start = i + 1;
} else if (start >= 0 && !flatModel[i].isHeader && flatModel[i].sectionId === sectionId) {
end = i;
} else if (start >= 0 && end >= 0 && flatModel[i].sectionId !== sectionId) {
break;
}
}
return {
start: start,
end: end,
count: end >= start ? end - start + 1 : 0
};
}
function getGridColumns(viewMode, gridColumns) {
switch (viewMode) {
case "tile":
return 3;
case "grid":
return gridColumns;
default:
return 1;
}
}
function calculateNextIndex(flatModel, selectedFlatIndex, sectionId, viewMode, gridColumns, getSectionViewModeFn) {
if (flatModel.length === 0)
return selectedFlatIndex;
var entry = flatModel[selectedFlatIndex];
if (!entry || entry.isHeader) {
var next = findNextNonHeaderIndex(flatModel, selectedFlatIndex + 1);
return next !== -1 ? next : selectedFlatIndex;
}
var actualViewMode = viewMode || getSectionViewModeFn(entry.sectionId);
if (actualViewMode === "list") {
var next = findNextNonHeaderIndex(flatModel, selectedFlatIndex + 1);
return next !== -1 ? next : selectedFlatIndex;
}
var bounds = getSectionBounds(flatModel, entry.sectionId);
var cols = getGridColumns(actualViewMode, gridColumns);
var posInSection = selectedFlatIndex - bounds.start;
var newPosInSection = posInSection + cols;
if (newPosInSection < bounds.count) {
return bounds.start + newPosInSection;
}
var nextSection = findNextNonHeaderIndex(flatModel, bounds.end + 1);
return nextSection !== -1 ? nextSection : selectedFlatIndex;
}
function calculatePrevIndex(flatModel, selectedFlatIndex, sectionId, viewMode, gridColumns, getSectionViewModeFn) {
if (flatModel.length === 0)
return selectedFlatIndex;
var entry = flatModel[selectedFlatIndex];
if (!entry || entry.isHeader) {
var prev = findPrevNonHeaderIndex(flatModel, selectedFlatIndex - 1);
return prev !== -1 ? prev : selectedFlatIndex;
}
var actualViewMode = viewMode || getSectionViewModeFn(entry.sectionId);
if (actualViewMode === "list") {
var prev = findPrevNonHeaderIndex(flatModel, selectedFlatIndex - 1);
return prev !== -1 ? prev : selectedFlatIndex;
}
var bounds = getSectionBounds(flatModel, entry.sectionId);
var cols = getGridColumns(actualViewMode, gridColumns);
var posInSection = selectedFlatIndex - bounds.start;
var newPosInSection = posInSection - cols;
if (newPosInSection >= 0) {
return bounds.start + newPosInSection;
}
var prevItem = findPrevNonHeaderIndex(flatModel, bounds.start - 1);
return prevItem !== -1 ? prevItem : selectedFlatIndex;
}
function calculateRightIndex(flatModel, selectedFlatIndex, getSectionViewModeFn) {
if (flatModel.length === 0)
return selectedFlatIndex;
var entry = flatModel[selectedFlatIndex];
if (!entry || entry.isHeader) {
var next = findNextNonHeaderIndex(flatModel, selectedFlatIndex + 1);
return next !== -1 ? next : selectedFlatIndex;
}
var viewMode = getSectionViewModeFn(entry.sectionId);
if (viewMode === "list") {
var next = findNextNonHeaderIndex(flatModel, selectedFlatIndex + 1);
return next !== -1 ? next : selectedFlatIndex;
}
var bounds = getSectionBounds(flatModel, entry.sectionId);
var posInSection = selectedFlatIndex - bounds.start;
if (posInSection + 1 < bounds.count) {
return bounds.start + posInSection + 1;
}
return selectedFlatIndex;
}
function calculateLeftIndex(flatModel, selectedFlatIndex, getSectionViewModeFn) {
if (flatModel.length === 0)
return selectedFlatIndex;
var entry = flatModel[selectedFlatIndex];
if (!entry || entry.isHeader) {
var prev = findPrevNonHeaderIndex(flatModel, selectedFlatIndex - 1);
return prev !== -1 ? prev : selectedFlatIndex;
}
var viewMode = getSectionViewModeFn(entry.sectionId);
if (viewMode === "list") {
var prev = findPrevNonHeaderIndex(flatModel, selectedFlatIndex - 1);
return prev !== -1 ? prev : selectedFlatIndex;
}
var bounds = getSectionBounds(flatModel, entry.sectionId);
var posInSection = selectedFlatIndex - bounds.start;
if (posInSection > 0) {
return bounds.start + posInSection - 1;
}
return selectedFlatIndex;
}
function calculateNextSectionIndex(flatModel, selectedFlatIndex) {
var currentSection = null;
if (selectedFlatIndex >= 0 && selectedFlatIndex < flatModel.length) {
currentSection = flatModel[selectedFlatIndex].sectionId;
}
var foundCurrent = false;
for (var i = 0; i < flatModel.length; i++) {
if (flatModel[i].isHeader) {
if (foundCurrent) {
for (var j = i + 1; j < flatModel.length; j++) {
if (!flatModel[j].isHeader)
return j;
}
}
if (flatModel[i].section.id === currentSection) {
foundCurrent = true;
}
}
}
return selectedFlatIndex;
}
function calculatePrevSectionIndex(flatModel, selectedFlatIndex) {
var currentSection = null;
if (selectedFlatIndex >= 0 && selectedFlatIndex < flatModel.length) {
currentSection = flatModel[selectedFlatIndex].sectionId;
}
var lastSectionStart = -1;
var prevSectionStart = -1;
for (var i = 0; i < flatModel.length; i++) {
if (flatModel[i].isHeader) {
if (flatModel[i].section.id === currentSection) {
break;
}
prevSectionStart = lastSectionStart;
lastSectionStart = i;
}
}
if (prevSectionStart >= 0) {
for (var j = prevSectionStart + 1; j < flatModel.length; j++) {
if (!flatModel[j].isHeader)
return j;
}
}
return selectedFlatIndex;
}
function calculatePageDownIndex(flatModel, selectedFlatIndex, visibleItems) {
if (flatModel.length === 0)
return selectedFlatIndex;
var itemsToSkip = visibleItems || 8;
var newIndex = selectedFlatIndex;
for (var i = 0; i < itemsToSkip; i++) {
var next = findNextNonHeaderIndex(flatModel, newIndex + 1);
if (next === -1)
break;
newIndex = next;
}
return newIndex;
}
function calculatePageUpIndex(flatModel, selectedFlatIndex, visibleItems) {
if (flatModel.length === 0)
return selectedFlatIndex;
var itemsToSkip = visibleItems || 8;
var newIndex = selectedFlatIndex;
for (var i = 0; i < itemsToSkip; i++) {
var prev = findPrevNonHeaderIndex(flatModel, newIndex - 1);
if (prev === -1)
break;
newIndex = prev;
}
return newIndex;
}

View File

@@ -9,7 +9,7 @@ Rectangle {
property var item: null
property bool isSelected: false
property bool isHovered: itemArea.containsMouse
property bool isHovered: itemArea.containsMouse || allModeToggleArea.containsMouse
property var controller: null
property int flatIndex: -1
@@ -35,9 +35,32 @@ Rectangle {
width: parent?.width ?? 200
height: 52
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryPressed : "transparent"
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
radius: Theme.cornerRadius
MouseArea {
id: itemArea
anchors.fill: parent
anchors.rightMargin: root.item?.type === "plugin_browse" ? 40 : 0
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
var scenePos = mapToItem(null, mouse.x, mouse.y);
root.rightClicked(scenePos.x, scenePos.y);
} else {
root.clicked();
}
}
onPositionChanged: {
if (root.controller)
root.controller.keyboardNavigationActive = false;
}
}
Row {
anchors.fill: parent
anchors.leftMargin: Theme.spacingM
@@ -86,7 +109,47 @@ Rectangle {
spacing: Theme.spacingS
Rectangle {
visible: root.item?.type && root.item.type !== "app"
id: allModeToggle
visible: root.item?.type === "plugin_browse"
width: 28
height: 28
radius: 14
anchors.verticalCenter: parent.verticalCenter
color: allModeToggleArea.containsMouse ? Theme.surfaceHover : "transparent"
property bool isAllowed: {
if (root.item?.type !== "plugin_browse")
return false;
var pluginId = root.item?.data?.pluginId;
if (!pluginId)
return false;
SettingsData.launcherPluginVisibility;
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
}
DankIcon {
anchors.centerIn: parent
name: allModeToggle.isAllowed ? "visibility" : "visibility_off"
size: 18
color: allModeToggle.isAllowed ? Theme.primary : Theme.surfaceVariantText
}
MouseArea {
id: allModeToggleArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
var pluginId = root.item?.data?.pluginId;
if (!pluginId)
return;
SettingsData.setPluginAllowWithoutTrigger(pluginId, !allModeToggle.isAllowed);
}
}
}
Rectangle {
visible: root.item?.type && root.item.type !== "app" && root.item.type !== "plugin_browse"
width: typeBadge.implicitWidth + Theme.spacingS * 2
height: 20
radius: 10
@@ -116,27 +179,4 @@ Rectangle {
}
}
}
MouseArea {
id: itemArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
var scenePos = mapToItem(null, mouse.x, mouse.y);
root.rightClicked(scenePos.x, scenePos.y);
} else {
root.clicked();
}
}
onPositionChanged: {
if (root.controller) {
root.controller.keyboardNavigationActive = false;
}
}
}
}

View File

@@ -159,6 +159,15 @@ Item {
contentHeight: sectionsColumn.height
clip: true
Component.onCompleted: {
verticalScrollBar.targetFlickable = mainFlickable;
verticalScrollBar.parent = root;
verticalScrollBar.z = 102;
verticalScrollBar.anchors.right = root.right;
verticalScrollBar.anchors.top = root.top;
verticalScrollBar.anchors.bottom = root.bottom;
}
Column {
id: sectionsColumn
width: parent.width

View File

@@ -16,7 +16,7 @@ const Weights = {
}
function tokenize(text) {
return text.toLowerCase().trim().split(/[\s\-_]+/).filter(function(w) { return w.length > 0 })
return text.toLowerCase().trim().split(/[\s\-_]+/).filter(function (w) { return w.length > 0 })
}
function hasWordBoundaryMatch(text, query) {
@@ -164,7 +164,7 @@ function scoreItems(items, query, getFrecencyFn) {
}
}
scored.sort(function(a, b) {
scored.sort(function (a, b) {
return b.score - a.score
})
@@ -204,7 +204,7 @@ function groupBySection(scoredItems, sectionOrder, sortAlphabetically, maxPerSec
var section = sections[sectionOrder[i].id]
if (section && section.items.length > 0) {
if (sortAlphabetically && section.id === "apps") {
section.items.sort(function(a, b) {
section.items.sort(function (a, b) {
return (a.name || "").localeCompare(b.name || "")
})
}

View File

@@ -19,7 +19,7 @@ Rectangle {
width: parent?.width ?? 200
height: 32
color: isSticky ? "transparent" : (hoverArea.containsMouse ? Theme.surfaceHover : "transparent")
radius: Theme.cornerRadius / 2
radius: Theme.cornerRadius
MouseArea {
id: hoverArea

View File

@@ -1,7 +1,9 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell.Wayland
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
@@ -21,9 +23,22 @@ Rectangle {
border.width: isSelected ? 2 : 0
border.color: Theme.primary
readonly property string toplevelId: item?.data?.toplevelId ?? ""
readonly property var waylandToplevel: {
if (!toplevelId || !item?.pluginId)
return null;
const pluginInstance = PluginService.pluginInstances[item.pluginId];
if (!pluginInstance?.getToplevelById)
return null;
return pluginInstance.getToplevelById(toplevelId);
}
readonly property bool hasScreencopy: waylandToplevel !== null
readonly property string iconValue: {
if (!item)
return "";
if (hasScreencopy)
return "";
var data = item.data;
if (data?.imageUrl)
return "image:" + data.imageUrl;
@@ -63,12 +78,26 @@ Rectangle {
color: Theme.surfaceContainerHigh
clip: true
ScreencopyView {
id: screencopyView
anchors.fill: parent
captureSource: root.waylandToplevel
live: root.hasScreencopy
visible: root.hasScreencopy
Rectangle {
anchors.fill: parent
color: root.isHovered ? Theme.withAlpha(Theme.surfaceVariant, 0.2) : "transparent"
}
}
AppIconRenderer {
anchors.fill: parent
iconValue: root.iconValue
iconSize: Math.min(parent.width, parent.height)
fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
materialIconSizeAdjustment: iconSize * 0.3
visible: !root.hasScreencopy
}
Rectangle {
@@ -109,6 +138,28 @@ Rectangle {
color: Theme.primaryText
}
}
Rectangle {
id: attributionBadge
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: Theme.spacingXS
width: root.hasScreencopy ? 28 : 40
height: root.hasScreencopy ? 28 : 16
radius: root.hasScreencopy ? 14 : 4
color: root.hasScreencopy ? Theme.surfaceContainer : "transparent"
visible: attributionImage.status === Image.Ready
opacity: 0.95
Image {
id: attributionImage
anchors.fill: parent
anchors.margins: root.hasScreencopy ? 4 : 0
fillMode: Image.PreserveAspectFit
source: root.item?.data?.attribution || ""
mipmap: true
}
}
}
}

View File

@@ -343,7 +343,7 @@ FloatingWindow {
id: sidebar
anchors.left: parent.left
width: settingsModal.isCompactMode ? parent.width : 270
width: settingsModal.isCompactMode ? parent.width : sidebar.implicitWidth
visible: settingsModal.isCompactMode ? settingsModal.menuVisible : true
parentModal: settingsModal
currentIndex: settingsModal.currentTabIndex

View File

@@ -483,11 +483,52 @@ Rectangle {
return -1;
}
width: 270
property real __maxTextWidth: Math.max(__m1.advanceWidth, __m2.advanceWidth, __m3.advanceWidth, __m4.advanceWidth, __m5.advanceWidth, __m6.advanceWidth)
property real __calculatedWidth: Math.max(270, __maxTextWidth + Theme.iconSize * 2 + Theme.spacingM * 4 + Theme.spacingS * 2)
implicitWidth: __calculatedWidth
width: __calculatedWidth
height: parent.height
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
StyledTextMetrics {
id: __m1
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
text: I18n.tr("Workspaces & Widgets")
}
StyledTextMetrics {
id: __m2
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
text: I18n.tr("Typography & Motion")
}
StyledTextMetrics {
id: __m3
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
text: I18n.tr("Keyboard Shortcuts")
}
StyledTextMetrics {
id: __m4
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
text: I18n.tr("Power & Security")
}
StyledTextMetrics {
id: __m5
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
text: I18n.tr("Dock & Launcher")
}
StyledTextMetrics {
id: __m6
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
text: I18n.tr("Personalization")
}
function selectSearchResult(result) {
if (!result)
return;
@@ -750,7 +791,7 @@ Rectangle {
Rectangle {
id: categoryRow
width: parent.width
height: 40
height: Math.max(Theme.iconSize, Theme.fontSizeMedium) + Theme.spacingS * 2
radius: Theme.cornerRadius
visible: categoryDelegate.modelData.separator !== true
@@ -769,10 +810,9 @@ Rectangle {
}
Row {
id: categoryRowContent
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
@@ -789,19 +829,18 @@ Rectangle {
font.weight: (categoryRow.isActive || root.isChildActive(categoryDelegate.modelData)) ? Font.Medium : Font.Normal
color: categoryRow.isActive ? Theme.primaryText : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
width: parent.width - Theme.iconSize - Theme.spacingM - (categoryDelegate.modelData.children ? expandIcon.width + Theme.spacingS : 0)
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
}
}
DankIcon {
id: expandIcon
name: root.isCategoryExpanded(categoryDelegate.modelData.id) ? "expand_less" : "expand_more"
size: Theme.iconSize - 4
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
visible: categoryDelegate.modelData.children !== undefined && categoryDelegate.modelData.children.length > 0
}
DankIcon {
id: expandIcon
name: root.isCategoryExpanded(categoryDelegate.modelData.id) ? "expand_less" : "expand_more"
size: Theme.iconSize - 4
color: Theme.surfaceVariantText
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
visible: categoryDelegate.modelData.children !== undefined && categoryDelegate.modelData.children.length > 0
}
MouseArea {
@@ -847,7 +886,7 @@ Rectangle {
readonly property bool isHighlighted: root.keyboardHighlightIndex === modelData.tabIndex
width: childrenColumn.width
height: 36
height: Math.max(Theme.iconSize - 4, Theme.fontSizeSmall + 1) + Theme.spacingS * 2
radius: Theme.cornerRadius
visible: root.isItemVisible(modelData)
color: {
@@ -861,6 +900,7 @@ Rectangle {
}
Row {
id: childRowContent
anchors.left: parent.left
anchors.leftMargin: Theme.spacingL + Theme.spacingM
anchors.verticalCenter: parent.verticalCenter

View File

@@ -0,0 +1,229 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Widgets
FloatingWindow {
id: root
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
objectName: "workspaceRenameModal"
title: I18n.tr("Rename Workspace")
minimumSize: Qt.size(400, 160)
maximumSize: Qt.size(400, 160)
color: Theme.surfaceContainer
visible: false
function show(name) {
nameInput.text = name;
visible = true;
Qt.callLater(() => nameInput.forceActiveFocus());
}
function hide() {
visible = false;
}
function submitAndClose() {
renameWorkspace(nameInput.text);
hide();
}
function renameWorkspace(name) {
if (CompositorService.isNiri) {
NiriService.renameWorkspace(name);
} else if (CompositorService.isHyprland) {
HyprlandService.renameWorkspace(name);
} else {
console.warn("WorkspaceRenameModal: rename not supported for this compositor");
}
}
onVisibleChanged: {
if (visible) {
Qt.callLater(() => nameInput.forceActiveFocus());
return;
}
nameInput.text = "";
}
FocusScope {
id: contentFocusScope
anchors.fill: parent
focus: true
Keys.onEscapePressed: event => {
hide();
event.accepted = true;
}
Column {
id: contentCol
anchors.centerIn: parent
width: parent.width - Theme.spacingL * 2
spacing: Theme.spacingM
Item {
width: contentCol.width
height: Math.max(headerText.height, buttonRow.height)
MouseArea {
anchors.left: parent.left
anchors.right: buttonRow.left
anchors.rightMargin: Theme.spacingM
height: parent.height
onPressed: windowControls.tryStartMove()
onDoubleClicked: windowControls.tryToggleMaximize()
}
StyledText {
id: headerText
text: I18n.tr("Enter a new name for this workspace")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
anchors.verticalCenter: parent.verticalCenter
width: parent.width - buttonRow.width - Theme.spacingM
}
Row {
id: buttonRow
anchors.right: parent.right
spacing: Theme.spacingXS
DankActionButton {
visible: windowControls.supported && windowControls.canMaximize
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: windowControls.tryToggleMaximize()
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: hide()
}
}
}
Rectangle {
width: parent.width
height: inputFieldHeight
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: nameInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: nameInput.activeFocus ? 2 : 1
MouseArea {
anchors.fill: parent
onClicked: nameInput.forceActiveFocus()
}
DankTextField {
id: nameInput
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
placeholderText: I18n.tr("Workspace name")
backgroundColor: "transparent"
enabled: root.visible
onAccepted: submitAndClose()
}
}
Item {
width: parent.width
height: 40
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
Rectangle {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
border.color: Theme.surfaceVariantAlpha
border.width: 1
StyledText {
id: cancelText
anchors.centerIn: parent
text: I18n.tr("Cancel")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
MouseArea {
id: cancelArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: hide()
}
}
Rectangle {
width: Math.max(80, renameText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: renameArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
StyledText {
id: renameText
anchors.centerIn: parent
text: I18n.tr("Rename")
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
}
MouseArea {
id: renameArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: submitAndClose()
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
}
FloatingWindowControls {
id: windowControls
targetWindow: root
}
IpcHandler {
target: "workspace-rename"
function open(): string {
const ws = NiriService.workspaces[NiriService.focusedWorkspaceId];
show(ws?.name || "");
return "WORKSPACE_RENAME_MODAL_OPENED";
}
function close(): string {
hide();
return "WORKSPACE_RENAME_MODAL_CLOSED";
}
}
}

View File

@@ -22,9 +22,9 @@ Item {
case "analog":
return 200;
case "stacked":
return 160;
return 100;
default:
return 280;
return 160;
}
}
property real defaultHeight: {
@@ -32,9 +32,9 @@ Item {
case "analog":
return 200;
case "stacked":
return 220;
default:
return 160;
default:
return 70;
}
}
property real minWidth: {
@@ -42,9 +42,9 @@ Item {
case "analog":
return 120;
case "stacked":
return 100;
return 70;
default:
return 140;
return 100;
}
}
property real minHeight: {
@@ -52,9 +52,9 @@ Item {
case "analog":
return 120;
case "stacked":
return 140;
default:
return 100;
default:
return 45;
}
}
@@ -84,7 +84,8 @@ Item {
readonly property color backgroundColor: Theme.withAlpha(Theme.surface, root.transparency)
readonly property bool showAnalogSeconds: isInstance ? (cfg.showAnalogSeconds ?? true) : SettingsData.desktopClockShowAnalogSeconds
readonly property bool needsSeconds: clockStyle === "analog" ? showAnalogSeconds : SettingsData.showSeconds
readonly property bool showDigitalSeconds: isInstance ? (cfg.showDigitalSeconds ?? false) : false
readonly property bool needsSeconds: clockStyle === "analog" ? showAnalogSeconds : showDigitalSeconds
SystemClock {
id: systemClock
@@ -298,12 +299,25 @@ Item {
Item {
id: digitalRoot
property real baseSize: Math.max(28, height * 0.38)
property real smallSize: Math.max(12, baseSize * 0.32)
property bool hasDate: root.showDate
property bool hasAmPm: !SettingsData.use24HourClock
property real verticalScale: hasDate && hasAmPm ? 0.55 : (hasDate || hasAmPm ? 0.65 : 0.8)
property real baseSize: Math.min(height * verticalScale, width * 0.22)
property real digitWidth: baseSize * 0.62
property real smallSize: baseSize * 0.35
property string hoursStr: {
const hours = SettingsData.use24HourClock ? systemClock.date?.getHours() ?? 0 : ((systemClock.date?.getHours() ?? 0) % 12 || 12);
if (SettingsData.use24HourClock || SettingsData.padHours12Hour)
return String(hours).padStart(2, '0');
return String(hours);
}
property string minutesStr: String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0')
property string secondsStr: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0')
Column {
anchors.centerIn: parent
spacing: 4
spacing: 0
StyledText {
visible: root.showDate
@@ -314,51 +328,86 @@ Item {
return systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? "";
}
font.pixelSize: digitalRoot.smallSize
color: root.accentColor
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: {
const hours = SettingsData.use24HourClock ? systemClock.date?.getHours() ?? 0 : ((systemClock.date?.getHours() ?? 0) % 12 || 12);
const minutes = String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0');
return hours + ":" + minutes;
}
font.pixelSize: digitalRoot.baseSize
font.weight: Font.Normal
color: root.accentColor
color: Theme.withAlpha(root.accentColor, 0.7)
}
Row {
visible: !SettingsData.use24HourClock || SettingsData.showSeconds
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingS
Row {
visible: SettingsData.showSeconds
spacing: Theme.spacingXS
DankIcon {
name: "timer"
size: Math.max(10, digitalRoot.baseSize * 0.25)
color: root.subtleTextColor
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0')
font.pixelSize: digitalRoot.smallSize
color: root.subtleTextColor
}
}
spacing: 0
StyledText {
visible: !SettingsData.use24HourClock
text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM"
font.pixelSize: digitalRoot.smallSize
visible: digitalRoot.hoursStr.length > 1
width: digitalRoot.digitWidth
text: digitalRoot.hoursStr.charAt(0)
font.pixelSize: digitalRoot.baseSize
font.weight: Font.Medium
color: root.accentColor
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: digitalRoot.digitWidth
text: digitalRoot.hoursStr.length > 1 ? digitalRoot.hoursStr.charAt(1) : digitalRoot.hoursStr.charAt(0)
font.pixelSize: digitalRoot.baseSize
font.weight: Font.Medium
color: root.accentColor
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: ":"
font.pixelSize: digitalRoot.baseSize
font.weight: Font.Medium
color: root.accentColor
}
StyledText {
width: digitalRoot.digitWidth
text: digitalRoot.minutesStr.charAt(0)
font.pixelSize: digitalRoot.baseSize
font.weight: Font.Medium
color: root.accentColor
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: digitalRoot.digitWidth
text: digitalRoot.minutesStr.charAt(1)
font.pixelSize: digitalRoot.baseSize
font.weight: Font.Medium
color: root.accentColor
horizontalAlignment: Text.AlignHCenter
}
StyledText {
visible: root.showDigitalSeconds
text: ":"
font.pixelSize: digitalRoot.baseSize
font.weight: Font.Medium
color: Theme.withAlpha(root.accentColor, 0.7)
}
StyledText {
visible: root.showDigitalSeconds
width: digitalRoot.digitWidth
text: digitalRoot.secondsStr.charAt(0)
font.pixelSize: digitalRoot.baseSize
font.weight: Font.Medium
color: Theme.withAlpha(root.accentColor, 0.7)
horizontalAlignment: Text.AlignHCenter
}
StyledText {
visible: root.showDigitalSeconds
width: digitalRoot.digitWidth
text: digitalRoot.secondsStr.charAt(1)
font.pixelSize: digitalRoot.baseSize
font.weight: Font.Medium
color: Theme.withAlpha(root.accentColor, 0.7)
horizontalAlignment: Text.AlignHCenter
}
}
StyledText {
visible: !SettingsData.use24HourClock
anchors.horizontalCenter: parent.horizontalCenter
text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM"
font.pixelSize: digitalRoot.smallSize
font.weight: Font.Medium
color: Theme.withAlpha(root.accentColor, 0.7)
}
}
}
@@ -370,78 +419,127 @@ Item {
Item {
id: stackedRoot
property real baseSize: Math.max(32, height * 0.32)
property real smallSize: Math.max(12, baseSize * 0.28)
property bool hasSeconds: root.showDigitalSeconds
property bool hasDate: root.showDate
property bool hasAmPm: !SettingsData.use24HourClock
property real extraContent: (hasSeconds ? 0.12 : 0) + (hasDate ? 0.08 : 0) + (hasAmPm ? 0.08 : 0)
property real baseSize: height * (0.42 - extraContent * 0.5)
property real digitWidth: baseSize * 0.58
property real smallSize: baseSize * 0.5
property real rowSpacing: -baseSize * 0.17
Column {
anchors.centerIn: parent
spacing: -baseSize * 0.1
spacing: 0
StyledText {
visible: root.showDate
Column {
spacing: stackedRoot.rowSpacing
anchors.horizontalCenter: parent.horizontalCenter
bottomPadding: Theme.spacingS
text: {
if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0)
return systemClock.date?.toLocaleDateString(Qt.locale(), SettingsData.clockDateFormat) ?? "";
return systemClock.date?.toLocaleDateString(Qt.locale(), "ddd, MMM d") ?? "";
}
font.pixelSize: stackedRoot.smallSize
color: root.accentColor
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: {
const hours = SettingsData.use24HourClock ? systemClock.date?.getHours() ?? 0 : ((systemClock.date?.getHours() ?? 0) % 12 || 12);
return String(hours).padStart(2, '0');
}
font.pixelSize: stackedRoot.baseSize
font.weight: Font.Normal
color: root.accentColor
lineHeight: 0.85
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0')
font.pixelSize: stackedRoot.baseSize
font.weight: Font.Normal
color: root.accentColor
lineHeight: 0.85
}
Row {
visible: SettingsData.showSeconds || !SettingsData.use24HourClock
anchors.horizontalCenter: parent.horizontalCenter
topPadding: Theme.spacingXS
spacing: Theme.spacingS
Row {
visible: SettingsData.showSeconds
spacing: Theme.spacingXS
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: "timer"
size: Math.max(10, stackedRoot.baseSize * 0.28)
color: root.subtleTextColor
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: {
if (SettingsData.use24HourClock)
return String(systemClock.date?.getHours() ?? 0).padStart(2, '0').charAt(0);
const hours = systemClock.date?.getHours() ?? 0;
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
return String(display).padStart(2, '0').charAt(0);
}
font.pixelSize: stackedRoot.baseSize
font.weight: Font.Medium
color: root.accentColor
width: stackedRoot.digitWidth
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0')
font.pixelSize: stackedRoot.smallSize
color: root.subtleTextColor
text: {
if (SettingsData.use24HourClock)
return String(systemClock.date?.getHours() ?? 0).padStart(2, '0').charAt(1);
const hours = systemClock.date?.getHours() ?? 0;
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
return String(display).padStart(2, '0').charAt(1);
}
font.pixelSize: stackedRoot.baseSize
font.weight: Font.Medium
color: root.accentColor
width: stackedRoot.digitWidth
horizontalAlignment: Text.AlignHCenter
}
}
Row {
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0').charAt(0)
font.pixelSize: stackedRoot.baseSize
font.weight: Font.Medium
color: root.accentColor
width: stackedRoot.digitWidth
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0').charAt(1)
font.pixelSize: stackedRoot.baseSize
font.weight: Font.Medium
color: root.accentColor
width: stackedRoot.digitWidth
horizontalAlignment: Text.AlignHCenter
}
}
}
Row {
visible: stackedRoot.hasSeconds
spacing: 0
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
visible: !SettingsData.use24HourClock
text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM"
text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0').charAt(0)
font.pixelSize: stackedRoot.smallSize
font.weight: Font.Medium
color: root.accentColor
color: Theme.withAlpha(root.accentColor, 0.7)
width: stackedRoot.smallSize * 0.58
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0').charAt(1)
font.pixelSize: stackedRoot.smallSize
font.weight: Font.Medium
color: Theme.withAlpha(root.accentColor, 0.7)
width: stackedRoot.smallSize * 0.58
horizontalAlignment: Text.AlignHCenter
}
}
Item {
width: 1
height: stackedRoot.baseSize * 0.1
visible: stackedRoot.hasDate
}
StyledText {
visible: stackedRoot.hasDate
anchors.horizontalCenter: parent.horizontalCenter
text: systemClock.date?.toLocaleDateString(Qt.locale(), "MMM dd") ?? ""
font.pixelSize: stackedRoot.smallSize * 0.7
color: Theme.withAlpha(root.accentColor, 0.7)
}
StyledText {
visible: stackedRoot.hasAmPm
anchors.horizontalCenter: parent.horizontalCenter
text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM"
font.pixelSize: stackedRoot.smallSize * 0.7
font.weight: Font.Medium
color: Theme.withAlpha(root.accentColor, 0.7)
}
}
}

View File

@@ -17,20 +17,19 @@ Rectangle {
property var widgetData: null
property bool editMode: false
signal clicked()
signal clicked
width: parent ? parent.width : 200
height: 60
radius: {
if (Theme.cornerRadius === 0) return 0
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4
if (Theme.cornerRadius === 0)
return 0;
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
}
readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgInactive:
Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
readonly property color _tileRingActive:
Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileBgActive: Theme.ccTileActiveBg
readonly property color _tileBgInactive: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
readonly property color _tileRingActive: Theme.ccTileRing
color: isActive ? _tileBgActive : _tileBgInactive
border.color: isActive ? _tileRingActive : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
@@ -38,8 +37,8 @@ Rectangle {
opacity: enabled ? 1.0 : 0.6
function hoverTint(base) {
const factor = 1.2
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor)
const factor = 1.2;
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
}
Rectangle {
@@ -49,7 +48,9 @@ Rectangle {
opacity: mouseArea.containsMouse ? 0.08 : 0.0
Behavior on opacity {
NumberAnimation { duration: Theme.shortDuration }
NumberAnimation {
duration: Theme.shortDuration
}
}
}
@@ -62,7 +63,7 @@ Rectangle {
DankIcon {
name: root.iconName
size: Theme.iconSize
color: isActive ? Theme.primaryText : Theme.primary
color: isActive ? Theme.ccTileActiveText : Theme.ccTileInactiveIcon
anchors.verticalCenter: parent.verticalCenter
}
@@ -80,7 +81,7 @@ Rectangle {
width: parent.width
text: root.text
style: Typography.Style.Body
color: isActive ? Theme.primaryText : Theme.surfaceText
color: isActive ? Theme.ccTileActiveText : Theme.surfaceText
elide: Text.ElideRight
wrapMode: Text.NoWrap
horizontalAlignment: Text.AlignLeft
@@ -90,7 +91,7 @@ Rectangle {
width: parent.width
text: root.secondaryText
style: Typography.Style.Caption
color: isActive ? Theme.primaryText : Theme.surfaceVariantText
color: isActive ? Theme.ccTileActiveText : Theme.surfaceVariantText
visible: text.length > 0
elide: Text.ElideRight
wrapMode: Text.NoWrap

View File

@@ -41,16 +41,16 @@ Rectangle {
readonly property color _labelPrimary: Theme.surfaceText
readonly property color _labelSecondary: Theme.surfaceVariantText
readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgActive: Theme.ccTileActiveBg
readonly property color _tileBgInactive: {
const transparency = Theme.popupTransparency;
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1);
return Qt.rgba(surface.r, surface.g, surface.b, transparency);
}
readonly property color _tileRingActive: Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileRingActive: Theme.ccTileRing
readonly property color _tileRingInactive: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.18)
readonly property color _tileIconActive: Theme.primaryText
readonly property color _tileIconInactive: Theme.primary
readonly property color _tileIconActive: Theme.ccTileActiveText
readonly property color _tileIconInactive: Theme.ccTileInactiveIcon
property int _padH: Theme.spacingS
property int _tileSize: 48

View File

@@ -27,11 +27,11 @@ Rectangle {
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
}
readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgActive: Theme.ccTileActiveBg
readonly property color _tileBgInactive: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
readonly property color _tileRingActive: Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileIconActive: Theme.primaryText
readonly property color _tileIconInactive: Theme.primary
readonly property color _tileRingActive: Theme.ccTileRing
readonly property color _tileIconActive: Theme.ccTileActiveText
readonly property color _tileIconInactive: Theme.ccTileInactiveIcon
color: {
if (isActive)

View File

@@ -73,7 +73,7 @@ Rectangle {
return Theme.error;
if (root.usagePercent > 75)
return Theme.warning;
return Theme.primary;
return Theme.ccTileInactiveIcon;
}
}
@@ -99,7 +99,7 @@ Rectangle {
return Theme.error;
if (root.usagePercent > 75)
return Theme.warning;
return Theme.primary;
return Theme.ccTileInactiveIcon;
}
}
}

View File

@@ -26,11 +26,11 @@ Rectangle {
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
}
readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgActive: Theme.ccTileActiveBg
readonly property color _tileBgInactive: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
readonly property color _tileRingActive: Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileIconActive: Theme.primaryText
readonly property color _tileIconInactive: Theme.primary
readonly property color _tileRingActive: Theme.ccTileRing
readonly property color _tileIconActive: Theme.ccTileActiveText
readonly property color _tileIconInactive: Theme.ccTileInactiveIcon
color: {
if (isActive)

View File

@@ -26,9 +26,9 @@ Rectangle {
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
}
readonly property color _tileBgActive: Theme.primary
readonly property color _tileBgActive: Theme.ccTileActiveBg
readonly property color _tileBgInactive: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
readonly property color _tileRingActive: Qt.rgba(Theme.primaryText.r, Theme.primaryText.g, Theme.primaryText.b, 0.22)
readonly property color _tileRingActive: Theme.ccTileRing
color: {
if (isActive)
@@ -69,7 +69,7 @@ Rectangle {
DankIcon {
name: root.iconName
size: Theme.iconSize
color: isActive ? Theme.primaryText : Theme.primary
color: isActive ? Theme.ccTileActiveText : Theme.ccTileInactiveIcon
anchors.verticalCenter: parent.verticalCenter
rotation: root.iconRotation
onRotationCompleted: root.iconRotationCompleted()
@@ -89,7 +89,7 @@ Rectangle {
width: parent.width
text: root.text
font.pixelSize: Theme.fontSizeMedium
color: isActive ? Theme.primaryText : Theme.surfaceText
color: isActive ? Theme.ccTileActiveText : Theme.surfaceText
font.weight: Font.Medium
elide: Text.ElideRight
wrapMode: Text.NoWrap
@@ -100,7 +100,7 @@ Rectangle {
width: parent.width
text: root.secondaryText
font.pixelSize: Theme.fontSizeSmall
color: isActive ? Theme.primaryText : Theme.surfaceVariantText
color: isActive ? Theme.ccTileActiveText : Theme.surfaceVariantText
visible: text.length > 0
elide: Text.ElideRight
wrapMode: Text.NoWrap

View File

@@ -19,6 +19,7 @@ Item {
property var rightWidgetsModel
readonly property real innerPadding: barConfig?.innerPadding ?? 4
readonly property real outlineThickness: (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0
property alias hLeftSection: hLeftSection
property alias hCenterSection: hCenterSection
@@ -30,8 +31,8 @@ Item {
anchors.fill: parent
anchors.leftMargin: Math.max(Theme.spacingXS, innerPadding * 0.8)
anchors.rightMargin: Math.max(Theme.spacingXS, innerPadding * 0.8)
anchors.topMargin: barWindow.isVertical ? (barWindow.hasAdjacentTopBar ? 0 : Theme.spacingXS) : 0
anchors.bottomMargin: barWindow.isVertical ? (barWindow.hasAdjacentBottomBar ? 0 : Theme.spacingXS) : 0
anchors.topMargin: barWindow.isVertical ? (barWindow.hasAdjacentTopBar ? outlineThickness : Theme.spacingXS) : 0
anchors.bottomMargin: barWindow.isVertical ? (barWindow.hasAdjacentBottomBar ? outlineThickness : Theme.spacingXS) : 0
clip: false
property int componentMapRevision: 0
@@ -302,6 +303,7 @@ Item {
"workspaceSwitcher": workspaceSwitcherComponent,
"focusedWindow": focusedWindowComponent,
"runningApps": runningAppsComponent,
"appsDock": appsDockComponent,
"clock": clockComponent,
"music": mediaComponent,
"weather": weatherComponent,
@@ -343,6 +345,7 @@ Item {
"workspaceSwitcherComponent": workspaceSwitcherComponent,
"focusedWindowComponent": focusedWindowComponent,
"runningAppsComponent": runningAppsComponent,
"appsDockComponent": appsDockComponent,
"clockComponent": clockComponent,
"mediaComponent": mediaComponent,
"weatherComponent": weatherComponent,
@@ -660,6 +663,21 @@ Item {
}
}
Component {
id: appsDockComponent
AppsDock {
widgetThickness: barWindow.widgetThickness
barThickness: barWindow.effectiveBarThickness
barSpacing: barConfig?.spacing ?? 4
section: topBarContent.getWidgetSection(parent)
parentScreen: barWindow.screen
topBar: topBarContent
barConfig: topBarContent.barConfig
isAutoHideBar: topBarContent.barConfig?.autoHide ?? false
}
}
Component {
id: clockComponent

View File

@@ -242,7 +242,8 @@ Loader {
"colorPicker": components.colorPickerComponent,
"systemUpdate": components.systemUpdateComponent,
"layout": components.layoutComponent,
"powerMenuButton": components.powerMenuButtonComponent
"powerMenuButton": components.powerMenuButtonComponent,
"appsDock": components.appsDockComponent
};
if (componentMap[widgetId]) {

View File

@@ -0,0 +1,867 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
property var widgetData: null
property var barConfig: null
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property string section: "left"
property var parentScreen
property var hoveredItem: null
property var topBar: null
property real widgetThickness: 30
property real barThickness: 48
property real barSpacing: 4
property bool isAutoHideBar: false
readonly property real horizontalPadding: (barConfig?.noBackground ?? false) ? 2 : Theme.spacingS
property Item windowRoot: (Window.window ? Window.window.contentItem : null)
property int draggedIndex: -1
property int dropTargetIndex: -1
property bool suppressShiftAnimation: false
property int pinnedAppCount: 0
readonly property real effectiveBarThickness: {
if (barThickness > 0 && barSpacing > 0) {
return barThickness + barSpacing;
}
const innerPadding = barConfig?.innerPadding ?? 4;
const spacing = barConfig?.spacing ?? 4;
return Math.max(26 + innerPadding * 0.6, Theme.barHeight - 4 - (8 - innerPadding)) + spacing;
}
readonly property var barBounds: {
if (!parentScreen || !barConfig) {
return {
"x": 0,
"y": 0,
"width": 0,
"height": 0,
"wingSize": 0
};
}
const barPosition = axis.edge === "left" ? 2 : (axis.edge === "right" ? 3 : (axis.edge === "top" ? 0 : 1));
return SettingsData.getBarBounds(parentScreen, effectiveBarThickness, barPosition, barConfig);
}
readonly property real barY: barBounds.y
readonly property real minTooltipY: {
if (!parentScreen || !isVertical) {
return 0;
}
if (isAutoHideBar) {
return 0;
}
if (parentScreen.y > 0) {
return effectiveBarThickness;
}
return 0;
}
// --- Dock Logic Helpers ---
function movePinnedApp(fromDockIndex, toDockIndex) {
if (fromDockIndex === toDockIndex)
return;
const currentPinned = [...(SessionData.barPinnedApps || [])];
if (fromDockIndex < 0 || fromDockIndex >= currentPinned.length || toDockIndex < 0 || toDockIndex >= currentPinned.length) {
return;
}
const movedApp = currentPinned.splice(fromDockIndex, 1)[0];
currentPinned.splice(toDockIndex, 0, movedApp);
SessionData.setBarPinnedApps(currentPinned);
}
property int _desktopEntriesUpdateTrigger: 0
property int _toplevelsUpdateTrigger: 0
property int _appIdSubstitutionsTrigger: 0
Connections {
target: CompositorService
function onToplevelsChanged() {
_toplevelsUpdateTrigger++;
updateModel();
}
}
Connections {
target: DesktopEntries
function onApplicationsChanged() {
_desktopEntriesUpdateTrigger++;
}
}
Connections {
target: SettingsData
function onAppIdSubstitutionsChanged() {
_appIdSubstitutionsTrigger++;
updateModel();
}
function onRunningAppsCurrentWorkspaceChanged() {
updateModel();
}
}
Connections {
target: SessionData
function onBarPinnedAppsChanged() {
root.suppressShiftAnimation = true;
root.draggedIndex = -1;
root.dropTargetIndex = -1;
updateModel();
Qt.callLater(() => {
root.suppressShiftAnimation = false;
});
}
}
property var dockItems: []
function isOnScreen(toplevel, screenName) {
if (!toplevel.screens)
return false;
for (let i = 0; i < toplevel.screens.length; i++) {
if (toplevel.screens[i]?.name === screenName)
return true;
}
return false;
}
function getCoreAppData(appId) {
if (typeof AppSearchService === "undefined")
return null;
const coreApps = AppSearchService.coreApps || [];
for (let i = 0; i < coreApps.length; i++) {
if (coreApps[i].builtInPluginId === appId)
return coreApps[i];
}
return null;
}
function getCoreAppDataByTitle(windowTitle) {
if (typeof AppSearchService === "undefined" || !windowTitle)
return null;
const coreApps = AppSearchService.coreApps || [];
for (let i = 0; i < coreApps.length; i++) {
if (coreApps[i].name === windowTitle)
return coreApps[i];
}
return null;
}
function updateModel() {
const items = [];
const pinnedApps = [...(SessionData.barPinnedApps || [])];
_toplevelsUpdateTrigger;
const allToplevels = CompositorService.sortedToplevels;
let sortedToplevels = allToplevels;
if (SettingsData.runningAppsCurrentWorkspace && parentScreen) {
sortedToplevels = CompositorService.filterCurrentWorkspace(allToplevels, parentScreen.name) || [];
}
const appGroups = new Map();
pinnedApps.forEach(rawAppId => {
const appId = Paths.moddedAppId(rawAppId);
const coreAppData = getCoreAppData(appId);
appGroups.set(appId, {
appId: appId,
isPinned: true,
windows: [],
isCoreApp: coreAppData !== null,
coreAppData: coreAppData
});
});
sortedToplevels.forEach((toplevel, index) => {
const rawAppId = toplevel.appId || "unknown";
let appId = Paths.moddedAppId(rawAppId);
let coreAppData = null;
if (rawAppId === "org.quickshell") {
coreAppData = getCoreAppDataByTitle(toplevel.title);
if (coreAppData) {
appId = coreAppData.builtInPluginId;
}
}
if (!appGroups.has(appId)) {
appGroups.set(appId, {
appId: appId,
isPinned: false,
windows: [],
isCoreApp: coreAppData !== null,
coreAppData: coreAppData
});
}
appGroups.get(appId).windows.push({
toplevel: toplevel,
index: index,
windowTitle: toplevel.title
});
});
const pinnedGroups = [];
const unpinnedGroups = [];
Array.from(appGroups.entries()).forEach(([appId, group]) => {
const firstWindow = group.windows.length > 0 ? group.windows[0] : null;
const item = {
uniqueKey: "grouped_" + appId,
type: "grouped",
appId: appId,
toplevel: firstWindow ? firstWindow.toplevel : null,
isPinned: group.isPinned,
isRunning: group.windows.length > 0,
windowCount: group.windows.length,
allWindows: group.windows,
isCoreApp: group.isCoreApp || false,
coreAppData: group.coreAppData || null
};
if (group.isPinned) {
pinnedGroups.push(item);
} else {
unpinnedGroups.push(item);
}
});
pinnedGroups.forEach(item => items.push(item));
if (pinnedGroups.length > 0 && unpinnedGroups.length > 0) {
items.push({
uniqueKey: "separator_grouped",
type: "separator",
appId: "__SEPARATOR__",
toplevel: null,
isPinned: false,
isRunning: false
});
}
unpinnedGroups.forEach(item => items.push(item));
root.pinnedAppCount = pinnedGroups.length;
dockItems = items;
}
Component.onCompleted: updateModel()
readonly property int calculatedSize: {
const count = dockItems.length;
if (count === 0)
return 0;
if (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) {
return count * 24 + (count - 1) * Theme.spacingXS + horizontalPadding * 2;
} else {
return count * (24 + Theme.spacingXS + 120) + (count - 1) * Theme.spacingXS + horizontalPadding * 2;
}
}
readonly property real realCalculatedSize: {
let total = horizontalPadding * 2;
const compact = (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode);
for (let i = 0; i < dockItems.length; i++) {
const item = dockItems[i];
let itemSize = 0;
if (item.type === "separator") {
itemSize = 8;
} else {
itemSize = compact ? 24 : (24 + Theme.spacingXS + 120);
}
total += itemSize;
if (i < dockItems.length - 1)
total += Theme.spacingXS;
}
return total;
}
width: dockItems.length > 0 ? (isVertical ? barThickness : realCalculatedSize) : 0
height: dockItems.length > 0 ? (isVertical ? realCalculatedSize : barThickness) : 0
visible: dockItems.length > 0
Item {
id: visualBackground
width: root.isVertical ? root.widgetThickness : root.realCalculatedSize
height: root.isVertical ? root.realCalculatedSize : root.widgetThickness
anchors.centerIn: parent
clip: false
Rectangle {
id: outline
anchors.centerIn: parent
width: {
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return parent.width + borderWidth * 2;
}
height: {
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return parent.height + borderWidth * 2;
}
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
color: "transparent"
border.width: (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0
border.color: {
if (!(barConfig?.widgetOutlineEnabled ?? false)) {
return "transparent";
}
const colorOption = barConfig?.widgetOutlineColor || "primary";
const opacity = barConfig?.widgetOutlineOpacity ?? 1.0;
switch (colorOption) {
case "surfaceText":
return Theme.withAlpha(Theme.surfaceText, opacity);
case "secondary":
return Theme.withAlpha(Theme.secondary, opacity);
case "primary":
return Theme.withAlpha(Theme.primary, opacity);
default:
return Theme.withAlpha(Theme.primary, opacity);
}
}
}
Rectangle {
id: background
anchors.fill: parent
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
color: {
if (dockItems.length === 0)
return "transparent";
if ((barConfig?.noBackground ?? false))
return "transparent";
const baseColor = Theme.widgetBaseBackgroundColor;
const transparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
if (Theme.widgetBackgroundHasAlpha) {
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * transparency);
}
return Theme.withAlpha(baseColor, transparency);
}
}
}
Loader {
id: layoutLoader
anchors.centerIn: parent
sourceComponent: root.isVertical ? columnLayout : rowLayout
}
Component {
id: rowLayout
Row {
spacing: Theme.spacingXS
Repeater {
id: repeater
model: ScriptModel {
values: root.dockItems
objectProp: "uniqueKey"
}
delegate: dockDelegate
}
}
}
Component {
id: columnLayout
Column {
spacing: Theme.spacingXS
Repeater {
model: ScriptModel {
values: root.dockItems
objectProp: "uniqueKey"
}
delegate: dockDelegate
}
}
}
Loader {
id: tooltipLoader
active: false
sourceComponent: DankTooltip {}
}
Component {
id: dockDelegate
Item {
id: delegateItem
property bool isSeparator: modelData.type === "separator"
readonly property real visualSize: isSeparator ? 8 : ((widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? 24 : (24 + Theme.spacingXS + 120))
readonly property real visualWidth: root.isVertical ? root.barThickness : visualSize
readonly property real visualHeight: root.isVertical ? visualSize : root.barThickness
width: visualWidth
height: visualHeight
z: (dragHandler.dragging) ? 100 : 0
// --- Drag and Drop Shift Animation Logic ---
property real shiftOffset: {
if (root.draggedIndex < 0 || !modelData.isPinned || isSeparator)
return 0;
if (index === root.draggedIndex)
return 0;
const dragIdx = root.draggedIndex;
const dropIdx = root.dropTargetIndex;
const myIdx = index;
const shiftAmount = visualSize + Theme.spacingXS;
if (dropIdx < 0)
return 0;
if (dragIdx < dropIdx && myIdx > dragIdx && myIdx <= dropIdx)
return -shiftAmount;
if (dragIdx > dropIdx && myIdx >= dropIdx && myIdx < dragIdx)
return shiftAmount;
return 0;
}
transform: Translate {
x: root.isVertical ? 0 : delegateItem.shiftOffset
y: root.isVertical ? delegateItem.shiftOffset : 0
Behavior on x {
enabled: !root.suppressShiftAnimation
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
}
Behavior on y {
enabled: !root.suppressShiftAnimation
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
}
}
Rectangle {
visible: isSeparator
width: root.isVertical ? root.barThickness * 0.6 : 2
height: root.isVertical ? 2 : root.barThickness * 0.6
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
radius: 1
anchors.centerIn: parent
}
Item {
id: appItem
visible: !isSeparator
anchors.fill: parent
property bool isFocused: {
if (modelData.type === "grouped") {
return modelData.allWindows.some(w => w.toplevel && w.toplevel.activated);
}
return modelData.toplevel ? modelData.toplevel.activated : false;
}
property var appId: modelData.appId
property int windowCount: modelData.windowCount || (modelData.isRunning ? 1 : 0)
property string windowTitle: {
if (modelData.type === "grouped") {
const active = modelData.allWindows.find(w => w.toplevel && w.toplevel.activated);
if (active)
return active.windowTitle || "(Unnamed)";
if (modelData.allWindows.length > 0)
return modelData.allWindows[0].windowTitle || "(Unnamed)";
return "";
}
return modelData.toplevel ? (modelData.toplevel.title || "(Unnamed)") : "";
}
property string tooltipText: {
root._desktopEntriesUpdateTrigger;
const moddedId = Paths.moddedAppId(appId);
const desktopEntry = moddedId ? DesktopEntries.heuristicLookup(moddedId) : null;
const appName = appId ? Paths.getAppName(appId, desktopEntry) : "Unknown";
if (modelData.type === "grouped" && windowCount > 1) {
return appName + " (" + windowCount + " windows)";
}
return appName + (windowTitle ? " • " + windowTitle : "");
}
transform: Translate {
x: (dragHandler.dragging && !root.isVertical) ? dragHandler.dragAxisOffset : 0
y: (dragHandler.dragging && root.isVertical) ? dragHandler.dragAxisOffset : 0
}
Rectangle {
id: visualContent
width: root.isVertical ? 24 : delegateItem.visualSize
height: root.isVertical ? delegateItem.visualSize : 24
anchors.centerIn: parent
radius: Theme.cornerRadius
color: {
if (appItem.isFocused) {
return mouseArea.containsMouse ? Theme.primarySelected : Theme.withAlpha(Theme.primary, 0.2);
}
return mouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent";
}
border.width: dragHandler.dragging ? 2 : 0
border.color: Theme.primary
opacity: dragHandler.dragging ? 0.8 : 1.0
AppIconRenderer {
id: coreIcon
readonly property bool isCompact: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
anchors.left: (root.isVertical || isCompact) ? undefined : parent.left
anchors.leftMargin: (root.isVertical || isCompact) ? 0 : Theme.spacingXS
anchors.top: (root.isVertical && !isCompact) ? parent.top : undefined
anchors.topMargin: (root.isVertical && !isCompact) ? Theme.spacingXS : 0
anchors.centerIn: (root.isVertical || isCompact) ? parent : undefined
iconSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
materialIconSizeAdjustment: 0
iconValue: {
if (!modelData || !modelData.isCoreApp || !modelData.coreAppData)
return "";
const appId = modelData.coreAppData.id || modelData.coreAppData.builtInPluginId;
if ((appId === "dms_settings" || appId === "dms_notepad" || appId === "dms_sysmon") && modelData.coreAppData.cornerIcon) {
return "material:" + modelData.coreAppData.cornerIcon;
}
return modelData.coreAppData.icon || "";
}
colorOverride: Theme.widgetIconColor
fallbackText: "?"
visible: iconValue !== ""
z: 2
}
IconImage {
id: iconImg
readonly property bool isCompact: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
anchors.left: (root.isVertical || isCompact) ? undefined : parent.left
anchors.leftMargin: (root.isVertical || isCompact) ? 0 : Theme.spacingXS
anchors.top: (root.isVertical && !isCompact) ? parent.top : undefined
anchors.topMargin: (root.isVertical && !isCompact) ? Theme.spacingXS : 0
anchors.centerIn: (root.isVertical || isCompact) ? parent : undefined
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
source: {
root._desktopEntriesUpdateTrigger;
root._appIdSubstitutionsTrigger;
if (!appItem.appId)
return "";
if (modelData.isCoreApp)
return ""; // Explicitly skip if core app to avoid flickering or wrong look ups
const moddedId = Paths.moddedAppId(appItem.appId);
const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
return Paths.getAppIcon(appItem.appId, desktopEntry);
}
smooth: true
mipmap: true
asynchronous: true
visible: status === Image.Ready && !coreIcon.visible
layer.enabled: appItem.appId === "org.quickshell"
layer.smooth: true
layer.mipmap: true
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: Theme.primary
}
z: 2
}
DankIcon {
readonly property bool isCompact: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
anchors.left: (root.isVertical || isCompact) ? undefined : parent.left
anchors.leftMargin: (root.isVertical || isCompact) ? 0 : Theme.spacingXS
anchors.top: (root.isVertical && !isCompact) ? parent.top : undefined
anchors.topMargin: (root.isVertical && !isCompact) ? Theme.spacingXS : 0
anchors.centerIn: (root.isVertical || isCompact) ? parent : undefined
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
name: "sports_esports"
color: Theme.widgetTextColor
visible: !iconImg.visible && !coreIcon.visible && Paths.isSteamApp(appItem.appId)
}
Text {
anchors.centerIn: parent
visible: !iconImg.visible && !coreIcon.visible && !Paths.isSteamApp(appItem.appId)
text: {
root._desktopEntriesUpdateTrigger;
if (!appItem.appId)
return "?";
const moddedId = Paths.moddedAppId(appItem.appId);
const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
const appName = Paths.getAppName(appItem.appId, desktopEntry);
return appName.charAt(0).toUpperCase();
}
font.pixelSize: 10
color: Theme.widgetTextColor
}
Rectangle {
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.rightMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? -2 : 2
anchors.bottomMargin: -2
width: 14
height: 14
radius: 7
color: Theme.primary
visible: modelData.type === "grouped" && appItem.windowCount > 1
z: 10
StyledText {
anchors.centerIn: parent
text: appItem.windowCount > 9 ? "9+" : appItem.windowCount
font.pixelSize: 9
color: Theme.surface
}
}
StyledText {
visible: !root.isVertical && !(widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
anchors.left: iconImg.right
anchors.leftMargin: Theme.spacingXS
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: appItem.windowTitle || appItem.appId
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
color: Theme.widgetTextColor
elide: Text.ElideRight
maximumLineCount: 1
}
Rectangle {
visible: modelData.isRunning
width: root.isVertical ? 2 : 20
height: root.isVertical ? 20 : 2
radius: 1
color: appItem.isFocused ? Theme.primary : Theme.surfaceText
opacity: appItem.isFocused ? 1 : 0.5
anchors.bottom: root.isVertical ? undefined : parent.bottom
anchors.right: root.isVertical ? parent.right : undefined
anchors.horizontalCenter: root.isVertical ? undefined : parent.horizontalCenter
anchors.verticalCenter: root.isVertical ? parent.verticalCenter : undefined
anchors.margins: 0
z: 5
}
}
}
// Handler for Drag Logic
Item {
id: dragHandler
anchors.fill: parent
property bool dragging: false
property point dragStartPos: Qt.point(0, 0)
property real dragAxisOffset: 0
property bool longPressing: false
Timer {
id: longPressTimer
interval: 500
repeat: false
onTriggered: {
if (modelData.isPinned) {
dragHandler.longPressing = true;
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: mouse => {
if (mouse.button === Qt.LeftButton && modelData.isPinned) {
dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
longPressTimer.start();
}
}
onReleased: mouse => {
longPressTimer.stop();
const wasDragging = dragHandler.dragging;
const didReorder = wasDragging && root.dropTargetIndex >= 0 && root.dropTargetIndex !== root.draggedIndex;
if (didReorder) {
root.movePinnedApp(root.draggedIndex, root.dropTargetIndex);
}
dragHandler.longPressing = false;
dragHandler.dragging = false;
dragHandler.dragAxisOffset = 0;
root.draggedIndex = -1;
root.dropTargetIndex = -1;
if (wasDragging || mouse.button !== Qt.LeftButton)
return;
if (wasDragging || mouse.button !== Qt.LeftButton)
return;
if (modelData.type === "grouped") {
if (modelData.windowCount === 0) {
if (modelData.isCoreApp && modelData.coreAppData) {
AppSearchService.executeCoreApp(modelData.coreAppData);
} else {
const moddedId = Paths.moddedAppId(modelData.appId);
const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
if (desktopEntry)
SessionService.launchDesktopEntry(desktopEntry);
}
} else if (modelData.windowCount === 1) {
if (modelData.allWindows[0].toplevel)
modelData.allWindows[0].toplevel.activate();
} else {
let currentIndex = -1;
for (var i = 0; i < modelData.allWindows.length; i++) {
if (modelData.allWindows[i].toplevel.activated) {
currentIndex = i;
break;
}
}
const nextIndex = (currentIndex + 1) % modelData.allWindows.length;
modelData.allWindows[nextIndex].toplevel.activate();
}
}
}
onPositionChanged: mouse => {
if (dragHandler.longPressing && !dragHandler.dragging) {
const distance = Math.sqrt(Math.pow(mouse.x - dragHandler.dragStartPos.x, 2) + Math.pow(mouse.y - dragHandler.dragStartPos.y, 2));
if (distance > 5) {
dragHandler.dragging = true;
root.draggedIndex = index;
root.dropTargetIndex = index;
}
}
if (!dragHandler.dragging)
return;
const axisOffset = root.isVertical ? (mouse.y - dragHandler.dragStartPos.y) : (mouse.x - dragHandler.dragStartPos.x);
dragHandler.dragAxisOffset = axisOffset;
const itemSize = (root.isVertical ? delegateItem.height : delegateItem.width) + Theme.spacingXS;
const slotOffset = Math.round(axisOffset / itemSize);
const newTargetIndex = Math.max(0, Math.min(root.pinnedAppCount - 1, index + slotOffset));
if (newTargetIndex !== root.dropTargetIndex) {
root.dropTargetIndex = newTargetIndex;
}
}
onEntered: {
root.hoveredItem = delegateItem;
if (isSeparator)
return;
tooltipLoader.active = true;
if (tooltipLoader.item) {
if (root.isVertical) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0;
const relativeY = globalPos.y - screenY;
const tooltipX = root.axis?.edge === "left" ? (Theme.barHeight + (barConfig?.spacing ?? 4) + Theme.spacingXS) : (root.parentScreen.width - Theme.barHeight - (barConfig?.spacing ?? 4) - Theme.spacingXS);
const isLeft = root.axis?.edge === "left";
const adjustedY = relativeY + root.minTooltipY;
const finalX = screenX + tooltipX;
tooltipLoader.item.show(appItem.tooltipText, finalX, adjustedY, root.parentScreen, isLeft, !isLeft);
} else {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height);
const screenHeight = root.parentScreen ? root.parentScreen.height : Screen.height;
const isBottom = root.axis?.edge === "bottom";
const tooltipY = isBottom ? (screenHeight - Theme.barHeight - (barConfig?.spacing ?? 4) - Theme.spacingXS - 35) : (Theme.barHeight + (barConfig?.spacing ?? 4) + Theme.spacingXS);
tooltipLoader.item.show(appItem.tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false);
}
}
}
onExited: {
if (root.hoveredItem === delegateItem) {
root.hoveredItem = null;
if (tooltipLoader.item)
tooltipLoader.item.hide();
tooltipLoader.active = false;
}
}
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
if (tooltipLoader.item) {
tooltipLoader.item.hide();
}
tooltipLoader.active = false;
contextMenuLoader.active = true;
if (contextMenuLoader.item) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const isBarVertical = root.axis?.isVertical ?? false;
const barEdge = root.axis?.edge ?? "top";
let x = globalPos.x;
let y = globalPos.y;
if (barEdge === "bottom") {
y = (root.parentScreen ? root.parentScreen.height : Screen.height) - root.effectiveBarThickness;
} else if (barEdge === "top") {
y = root.effectiveBarThickness;
} else if (barEdge === "left") {
x = root.effectiveBarThickness;
} else if (barEdge === "right") {
x = (root.parentScreen ? root.parentScreen.width : Screen.width) - root.effectiveBarThickness;
}
const shouldHidePin = modelData.appId === "org.quickshell";
const moddedId = Paths.moddedAppId(modelData.appId);
const desktopEntry = moddedId ? DesktopEntries.heuristicLookup(moddedId) : null;
contextMenuLoader.item.showAt(x, y, isBarVertical, barEdge, modelData, shouldHidePin, desktopEntry, root.parentScreen);
}
}
}
}
}
}
}
Loader {
id: contextMenuLoader
active: false
source: "AppsDockContextMenu.qml"
}
}

View File

@@ -0,0 +1,431 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Widgets
import qs.Common
import qs.Services
import qs.Widgets
PanelWindow {
id: root
WlrLayershell.namespace: "dms:dock-context-menu"
property var appData: null
property var anchorItem: null
property int margin: 10
property bool hidePin: false
property var desktopEntry: null
property bool isDmsWindow: appData?.appId === "org.quickshell"
property bool isVertical: false
property string edge: "top"
property point anchorPos: Qt.point(0, 0)
function showAt(x, y, vertical, barEdge, data, hidePinOption, entry, targetScreen) {
if (targetScreen) {
root.screen = targetScreen;
}
anchorPos = Qt.point(x, y);
isVertical = vertical ?? false;
edge = barEdge ?? "top";
appData = data;
hidePin = hidePinOption || false;
desktopEntry = entry || null;
visible = true;
}
function close() {
visible = false;
}
screen: null
visible: false
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent"
anchors {
top: true
left: true
right: true
bottom: true
}
Rectangle {
id: menuContainer
x: {
if (root.isVertical) {
if (root.edge === "left") {
return Math.min(root.width - width - 10, root.anchorPos.x);
} else {
return Math.max(10, root.anchorPos.x - width);
}
} else {
const left = 10;
const right = root.width - width - 10;
const want = root.anchorPos.x - width / 2;
return Math.max(left, Math.min(right, want));
}
}
y: {
if (root.isVertical) {
const top = 10;
const bottom = root.height - height - 10;
const want = root.anchorPos.y - height / 2;
return Math.max(top, Math.min(bottom, want));
} else {
if (root.edge === "top") {
return Math.min(root.height - height - 10, root.anchorPos.y);
} else {
return Math.max(10, root.anchorPos.y - height);
}
}
}
width: Math.min(400, Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2))
height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2)
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
opacity: root.visible ? 1 : 0
visible: opacity > 0
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
}
}
Rectangle {
anchors.fill: parent
anchors.topMargin: 4
anchors.leftMargin: 2
anchors.rightMargin: -2
anchors.bottomMargin: -4
radius: parent.radius
color: Qt.rgba(0, 0, 0, 0.15)
z: -1
}
Column {
id: menuColumn
width: parent.width - Theme.spacingS * 2
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: Theme.spacingS
spacing: 1
// Window list for grouped apps
Repeater {
model: {
if (!root.appData || root.appData.type !== "grouped")
return [];
const toplevels = [];
const allToplevels = ToplevelManager.toplevels.values;
for (let i = 0; i < allToplevels.length; i++) {
const toplevel = allToplevels[i];
if (toplevel.appId === root.appData.appId) {
toplevels.push(toplevel);
}
}
return toplevels;
}
Rectangle {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: windowArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.right: closeButton.left
anchors.rightMargin: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
text: (modelData && modelData.title) ? modelData.title : I18n.tr("(Unnamed)")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
Rectangle {
id: closeButton
anchors.right: parent.right
anchors.rightMargin: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
width: 20
height: 20
radius: 10
color: closeMouseArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.2) : "transparent"
DankIcon {
anchors.centerIn: parent
name: "close"
size: 12
color: closeMouseArea.containsMouse ? Theme.error : Theme.surfaceText
}
MouseArea {
id: closeMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData && modelData.close) {
modelData.close();
}
root.close();
}
}
}
MouseArea {
id: windowArea
anchors.fill: parent
anchors.rightMargin: 24
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData && modelData.activate) {
modelData.activate();
}
root.close();
}
}
}
}
Rectangle {
visible: {
if (!root.appData)
return false;
if (root.appData.type !== "grouped")
return false;
return root.appData.windowCount > 0;
}
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
Repeater {
model: root.desktopEntry && root.desktopEntry.actions ? root.desktopEntry.actions : []
Rectangle {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: actionArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
Item {
anchors.verticalCenter: parent.verticalCenter
width: 16
height: 16
visible: modelData.icon && modelData.icon !== ""
IconImage {
anchors.fill: parent
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
}
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: modelData.name || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
}
MouseArea {
id: actionArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData) {
SessionService.launchDesktopAction(root.desktopEntry, modelData);
}
root.close();
}
}
}
}
Rectangle {
visible: {
if (!root.desktopEntry?.actions || root.desktopEntry.actions.length === 0) {
return false;
}
return !root.hidePin || (!root.isDmsWindow && root.desktopEntry && SessionService.nvidiaCommand);
}
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
Rectangle {
visible: !root.hidePin
width: parent.width
height: 28
radius: Theme.cornerRadius
color: pinArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: root.appData && root.appData.isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
MouseArea {
id: pinArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!root.appData) {
return;
}
if (root.appData.isPinned) {
SessionData.removeBarPinnedApp(root.appData.appId);
} else {
SessionData.addBarPinnedApp(root.appData.appId);
}
root.close();
}
}
}
Rectangle {
visible: {
const hasNvidia = !root.isDmsWindow && root.desktopEntry && SessionService.nvidiaCommand;
const hasWindow = root.appData && (root.appData.type === "window" || (root.appData.type === "grouped" && root.appData.windowCount > 0));
const hasPinOption = !root.hidePin;
const hasContentAbove = hasPinOption || hasNvidia;
return hasContentAbove && hasWindow;
}
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
Rectangle {
visible: !root.isDmsWindow && root.desktopEntry && SessionService.nvidiaCommand
width: parent.width
height: 28
radius: Theme.cornerRadius
color: nvidiaArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Launch on dGPU")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
MouseArea {
id: nvidiaArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.desktopEntry) {
SessionService.launchDesktopEntry(root.desktopEntry, true);
}
root.close();
}
}
}
Rectangle {
visible: root.appData && (root.appData.type === "window" || (root.appData.type === "grouped" && root.appData.windowCount > 0))
width: parent.width
height: 28
radius: Theme.cornerRadius
color: closeArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: {
if (root.appData && root.appData.type === "grouped") {
return I18n.tr("Close All Windows");
}
return I18n.tr("Close Window");
}
font.pixelSize: Theme.fontSizeSmall
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Normal
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.appData?.type === "window") {
root.appData?.toplevel?.close();
} else if (root.appData?.type === "grouped") {
root.appData?.allWindows?.forEach(window => window.toplevel?.close());
}
root.close();
}
}
}
}
}
MouseArea {
anchors.fill: parent
z: -1
onClicked: root.close()
}
}

View File

@@ -30,13 +30,11 @@ BasePill {
StyledText {
text: {
if (SettingsData.use24HourClock) {
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(0);
} else {
const hours = systemClock?.date?.getHours();
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
return String(display).padStart(2, '0').charAt(0);
}
const hours = systemClock?.date?.getHours();
if (SettingsData.use24HourClock)
return String(hours).padStart(2, '0').charAt(0);
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
return String(display).padStart(2, '0').charAt(0);
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
color: Theme.widgetTextColor
@@ -47,13 +45,11 @@ BasePill {
StyledText {
text: {
if (SettingsData.use24HourClock) {
return String(systemClock?.date?.getHours()).padStart(2, '0').charAt(1);
} else {
const hours = systemClock?.date?.getHours();
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
return String(display).padStart(2, '0').charAt(1);
}
const hours = systemClock?.date?.getHours();
if (SettingsData.use24HourClock)
return String(hours).padStart(2, '0').charAt(1);
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
return String(display).padStart(2, '0').charAt(1);
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
color: Theme.widgetTextColor
@@ -203,14 +199,101 @@ BasePill {
anchors.centerIn: parent
spacing: Theme.spacingS
StyledText {
id: timeText
text: {
return systemClock?.date?.toLocaleTimeString(Qt.locale(), SettingsData.getEffectiveTimeFormat());
property real fontSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
property real digitWidth: fontSize * 0.6
property string hoursStr: {
const hours = systemClock?.date?.getHours() ?? 0;
if (SettingsData.use24HourClock)
return String(hours).padStart(2, '0');
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
if (SettingsData.padHours12Hour)
return String(display).padStart(2, '0');
return String(display);
}
property string minutesStr: String(systemClock?.date?.getMinutes() ?? 0).padStart(2, '0')
property string secondsStr: String(systemClock?.date?.getSeconds() ?? 0).padStart(2, '0')
property string ampmStr: {
if (SettingsData.use24HourClock)
return "";
const hours = systemClock?.date?.getHours() ?? 0;
return hours >= 12 ? " PM" : " AM";
}
Row {
spacing: 0
anchors.verticalCenter: parent.verticalCenter
StyledText {
visible: clockRow.hoursStr.length > 1
text: clockRow.hoursStr.charAt(0)
font.pixelSize: clockRow.fontSize
color: Theme.widgetTextColor
width: clockRow.digitWidth
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: clockRow.hoursStr.length > 1 ? clockRow.hoursStr.charAt(1) : clockRow.hoursStr.charAt(0)
font.pixelSize: clockRow.fontSize
color: Theme.widgetTextColor
width: clockRow.digitWidth
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: ":"
font.pixelSize: clockRow.fontSize
color: Theme.widgetTextColor
}
StyledText {
text: clockRow.minutesStr.charAt(0)
font.pixelSize: clockRow.fontSize
color: Theme.widgetTextColor
width: clockRow.digitWidth
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: clockRow.minutesStr.charAt(1)
font.pixelSize: clockRow.fontSize
color: Theme.widgetTextColor
width: clockRow.digitWidth
horizontalAlignment: Text.AlignHCenter
}
StyledText {
visible: SettingsData.showSeconds
text: ":"
font.pixelSize: clockRow.fontSize
color: Theme.widgetTextColor
}
StyledText {
visible: SettingsData.showSeconds
text: clockRow.secondsStr.charAt(0)
font.pixelSize: clockRow.fontSize
color: Theme.widgetTextColor
width: clockRow.digitWidth
horizontalAlignment: Text.AlignHCenter
}
StyledText {
visible: SettingsData.showSeconds
text: clockRow.secondsStr.charAt(1)
font.pixelSize: clockRow.fontSize
color: Theme.widgetTextColor
width: clockRow.digitWidth
horizontalAlignment: Text.AlignHCenter
}
StyledText {
visible: !SettingsData.use24HourClock
text: clockRow.ampmStr
font.pixelSize: clockRow.fontSize
color: Theme.widgetTextColor
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
color: Theme.widgetTextColor
anchors.baseline: dateText.baseline
}
StyledText {
@@ -218,7 +301,7 @@ BasePill {
text: "•"
font.pixelSize: Theme.fontSizeSmall
color: Theme.outlineButton
anchors.baseline: dateText.baseline
anchors.verticalCenter: parent.verticalCenter
visible: !compact
}
@@ -230,7 +313,7 @@ BasePill {
}
return systemClock?.date?.toLocaleDateString(Qt.locale(), "ddd d");
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
font.pixelSize: clockRow.fontSize
color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter
visible: !compact

View File

@@ -504,22 +504,21 @@ Item {
readonly property real padding: Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
readonly property real visualWidth: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2)
readonly property real visualHeight: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight
readonly property real appIconSize: Theme.barIconSize(barThickness, -6, root.barConfig?.noBackground)
readonly property real appIconSize: Theme.barIconSize(barThickness, -6 + SettingsData.workspaceAppIconSizeOffset, root.barConfig?.noBackground)
function getRealWorkspaces() {
return root.workspaceList.filter(ws => {
if (useExtWorkspace)
return ws && (ws.id !== "" || ws.name !== "") && !ws.hidden;
if (CompositorService.isNiri)
return ws && ws.idx !== -1;
if (CompositorService.isHyprland)
return ws && ws.id !== -1;
if (CompositorService.isDwl)
return ws && ws.tag !== -1;
if (CompositorService.isSway || CompositorService.isScroll)
return ws && ws.num !== -1;
return ws !== -1;
if (useExtWorkspace)
return ws && (ws.id !== "" || ws.name !== "") && !ws.hidden;
if (CompositorService.isNiri)
return ws && ws.idx !== -1;
if (CompositorService.isHyprland)
return ws && ws.id !== -1;
if (CompositorService.isDwl)
return ws && ws.tag !== -1;
if (CompositorService.isSway || CompositorService.isScroll)
return ws && ws.num !== -1;
return ws !== -1;
});
}
@@ -864,35 +863,91 @@ Item {
property bool loadedHasIcon: false
property var loadedIcons: []
readonly property real baseWidth: root.isVertical ? (SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5) : (isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7)
readonly property real baseHeight: root.isVertical ? (isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7) : (SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5)
readonly property int stableIconCount: {
if (!SettingsData.showWorkspaceApps || isPlaceholder)
return 0;
let targetWorkspaceId;
if (root.useExtWorkspace) {
targetWorkspaceId = modelData?.id || modelData?.name;
} else if (CompositorService.isNiri) {
targetWorkspaceId = modelData?.id;
} else if (CompositorService.isHyprland) {
targetWorkspaceId = modelData?.id;
} else if (CompositorService.isDwl) {
targetWorkspaceId = modelData?.tag;
} else if (CompositorService.isSway || CompositorService.isScroll) {
targetWorkspaceId = modelData?.num;
}
if (targetWorkspaceId === undefined || targetWorkspaceId === null)
return 0;
const wins = CompositorService.isNiri ? (NiriService.windows || []) : CompositorService.sortedToplevels;
const seen = {};
let groupedCount = 0;
let totalCount = 0;
for (let i = 0; i < wins.length; i++) {
const w = wins[i];
if (!w)
continue;
let winWs = null;
if (CompositorService.isNiri) {
winWs = w.workspace_id;
} else if (CompositorService.isSway || CompositorService.isScroll) {
winWs = w.workspace?.num;
} else if (CompositorService.isHyprland) {
const hyprlandToplevels = Array.from(Hyprland.toplevels?.values || []);
const hyprToplevel = hyprlandToplevels.find(ht => ht.wayland === w);
winWs = hyprToplevel?.workspace?.id;
}
if (winWs !== targetWorkspaceId)
continue;
totalCount++;
const appKey = w.app_id || w.appId || w.class || w.windowClass || "unknown";
if (!seen[appKey]) {
seen[appKey] = true;
groupedCount++;
}
}
return SettingsData.groupWorkspaceApps ? groupedCount : totalCount;
}
readonly property real baseWidth: root.isVertical ? (SettingsData.showWorkspaceApps ? Math.max(widgetHeight * 0.7, root.appIconSize + Theme.spacingXS * 2) : widgetHeight * 0.5) : (isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7)
readonly property real baseHeight: root.isVertical ? (isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7) : (SettingsData.showWorkspaceApps ? Math.max(widgetHeight * 0.7, root.appIconSize + Theme.spacingXS * 2) : widgetHeight * 0.5)
readonly property bool hasWorkspaceName: SettingsData.showWorkspaceName && modelData?.name && modelData.name !== ""
readonly property bool workspaceNamesEnabled: SettingsData.showWorkspaceName && CompositorService.isNiri
readonly property real contentImplicitWidth: (hasWorkspaceName || loadedHasIcon) ? (appIconsLoader.item?.contentWidth ?? 0) : 0
readonly property real contentImplicitHeight: (workspaceNamesEnabled || loadedHasIcon) ? (appIconsLoader.item?.contentHeight ?? 0) : 0
readonly property real iconsExtraWidth: {
if (!root.isVertical && SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
if (!root.isVertical && SettingsData.showWorkspaceApps && stableIconCount > 0) {
const numIcons = Math.min(stableIconCount, SettingsData.maxWorkspaceIcons);
return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
}
return 0;
}
readonly property real iconsExtraHeight: {
if (root.isVertical && SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
if (root.isVertical && SettingsData.showWorkspaceApps && stableIconCount > 0) {
const numIcons = Math.min(stableIconCount, SettingsData.maxWorkspaceIcons);
return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
}
return 0;
}
readonly property real visualWidth: {
if (contentImplicitWidth <= 0) return baseWidth + iconsExtraWidth;
if (contentImplicitWidth <= 0)
return baseWidth + iconsExtraWidth;
const padding = root.isVertical ? Theme.spacingXS : Theme.spacingS;
return Math.max(baseWidth + iconsExtraWidth, contentImplicitWidth + padding);
}
readonly property real visualHeight: {
if (contentImplicitHeight <= 0) return baseHeight + iconsExtraHeight;
if (contentImplicitHeight <= 0)
return baseHeight + iconsExtraHeight;
const padding = root.isVertical ? Theme.spacingS : Theme.spacingXS;
return Math.max(baseHeight + iconsExtraHeight, contentImplicitHeight + padding);
}
@@ -1080,6 +1135,20 @@ Item {
width: root.isVertical ? root.widgetHeight : visualWidth
height: root.isVertical ? visualHeight : root.widgetHeight
Behavior on width {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on height {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Rectangle {
id: focusedBorderRing
x: root.isVertical ? (root.widgetHeight - width) / 2 : (parent.width - width) / 2
@@ -1349,6 +1418,15 @@ Item {
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
}
StyledText {
visible: (SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon
anchors.horizontalCenter: parent.horizontalCenter
text: root.getWorkspaceIndex(modelData, index)
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
}
Repeater {
model: ScriptModel {
values: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)

View File

@@ -314,25 +314,39 @@ DankPopout {
height: Theme.spacingXS
}
StackLayout {
Item {
id: pages
width: parent.width
height: implicitHeight
implicitHeight: {
if (currentIndex === 0)
if (root.currentTabIndex === 0)
return overviewLoader.item?.implicitHeight ?? 410;
if (currentIndex === 1)
if (root.currentTabIndex === 1)
return mediaLoader.item?.implicitHeight ?? 410;
if (currentIndex === 2)
if (root.currentTabIndex === 2)
return wallpaperLoader.item?.implicitHeight ?? 410;
if (SettingsData.weatherEnabled && currentIndex === 3)
if (SettingsData.weatherEnabled && root.currentTabIndex === 3)
return weatherLoader.item?.implicitHeight ?? 410;
return 410;
}
currentIndex: root.currentTabIndex
readonly property var currentItem: {
if (root.currentTabIndex === 0)
return overviewLoader.item;
if (root.currentTabIndex === 1)
return mediaLoader.item;
if (root.currentTabIndex === 2)
return wallpaperLoader.item;
if (root.currentTabIndex === 3)
return weatherLoader.item;
return null;
}
Loader {
id: overviewLoader
anchors.fill: parent
active: root.currentTabIndex === 0
visible: active
sourceComponent: Component {
OverviewTab {
onCloseDash: root.dashVisible = false
@@ -350,7 +364,9 @@ DankPopout {
Loader {
id: mediaLoader
anchors.fill: parent
active: root.currentTabIndex === 1
visible: active
sourceComponent: Component {
MediaPlayerTab {
targetScreen: root.screen
@@ -379,7 +395,9 @@ DankPopout {
Loader {
id: wallpaperLoader
anchors.fill: parent
active: root.currentTabIndex === 2
visible: active
sourceComponent: Component {
WallpaperTab {
active: true
@@ -393,7 +411,9 @@ DankPopout {
Loader {
id: weatherLoader
anchors.fill: parent
active: SettingsData.weatherEnabled && root.currentTabIndex === 3
visible: active
sourceComponent: Component {
WeatherTab {}
}

View File

@@ -129,7 +129,7 @@ Item {
}
implicitWidth: 700
implicitHeight: 410
implicitHeight: playerContent.height + playerContent.anchors.topMargin * 2
Connections {
target: activePlayer
@@ -327,6 +327,7 @@ Item {
clip: false
visible: !_noneAvailable && (!showNoPlayerNow)
ColumnLayout {
id: playerContent
width: 484
height: 370
spacing: Theme.spacingXS

View File

@@ -12,7 +12,7 @@ Item {
LayoutMirroring.childrenInherit: true
implicitWidth: 700
implicitHeight: 410
implicitHeight: root.available ? mainColumn.implicitHeight : unavailableColumn.implicitHeight + Theme.spacingXL * 2
property bool syncing: false
function syncFrom(type) {
@@ -52,6 +52,7 @@ Item {
property bool available: WeatherService.weather.available
Column {
id: unavailableColumn
anchors.centerIn: parent
spacing: Theme.spacingL
visible: !root.available
@@ -141,6 +142,7 @@ Item {
}
Column {
id: mainColumn
anchors.fill: parent
visible: root.available
spacing: Theme.spacingXS
@@ -164,7 +166,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
// anchors.verticalCenter: parent.verticalCenter
width: weatherIcon.width + tempColumn.width + sunriseColumn.width + Theme.spacingM * 2
height: 70
height: Math.max(weatherIcon.height, tempColumn.height, sunriseColumn.height)
DankIcon {
id: weatherIcon
@@ -325,7 +327,7 @@ Item {
Item {
id: dateStepper
anchors.horizontalCenter: parent.horizontalCenter
height: 60
height: dateStepperInner.height + Theme.spacingM * 2
width: dateStepperInner.width
property var currentDate: new Date()
@@ -354,10 +356,11 @@ Item {
Item {
id: dateStepperInner
anchors.fill: parent
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
readonly property var space: Theme.spacingXS
width: yearStepper.width + monthStepper.width + dayStepper.width + hourStepper.width + minuteStepper.width + (suffix.visible ? suffix.width : 0) + 10.5 * space + 2 * dateStepperInnerPadding.width
height: Math.max(yearStepper.height, monthStepper.height, dayStepper.height, hourStepper.height, minuteStepper.height)
Item {
id: dateStepperInnerPadding
@@ -444,20 +447,15 @@ Item {
text: ":"
}
}
Rectangle {
StyledText {
id: suffix
visible: !SettingsData.use24HourClock
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenter: minuteStepper.verticalCenter
anchors.left: minuteStepper.right
anchors.leftMargin: 2 * parent.space
StyledText {
isMonospace: true
anchors.horizontalCenter: parent.horizontalCenter
text: dateStepper.splitDate[5] ?? ""
font.pixelSize: Theme.fontSizeSmall
x: -Theme.fontSizeSmall / 2
y: -Theme.fontSizeSmall / 2
}
isMonospace: true
text: dateStepper.splitDate[5] ?? ""
font.pixelSize: Theme.fontSizeSmall
}
DankActionButton {
id: dateResetButton
@@ -898,7 +896,7 @@ Item {
Row {
width: parent.width
height: 32
height: Math.max(hourlyHeader.height, denseButton.height) + Theme.spacingS
spacing: Theme.spacingS
StyledText {
@@ -928,7 +926,7 @@ Item {
Item {
width: parent.width
height: 100 + Theme.spacingXS
height: (hourlyLoader.item?.cardHeight ?? (Theme.fontSizeLarge * 6)) + Theme.spacingXS
Loader {
id: hourlyLoader
@@ -962,7 +960,7 @@ Item {
contentHeight: cardHeight
contentWidth: cardWidth
property var cardHeight: 100
property var cardHeight: Theme.fontSizeLarge * 6
property var cardWidth: ((hourlyList.width + hourlyList.spacing) / hourlyList.visibleCount) - hourlyList.spacing
property int initialIndex: (new Date()).getHours()
property bool dense: !SessionData.weatherHourlyDetailed
@@ -1038,7 +1036,7 @@ Item {
Row {
width: parent.width
height: 32
height: dailyHeader.height + Theme.spacingS
spacing: Theme.spacingS
StyledText {
@@ -1060,7 +1058,7 @@ Item {
Item {
width: parent.width
height: 100 + Theme.spacingXS
height: (dailyLoader.item?.cardHeight ?? (Theme.fontSizeLarge * 6)) + Theme.spacingXS
Loader {
id: dailyLoader
@@ -1094,7 +1092,7 @@ Item {
contentHeight: cardHeight
contentWidth: cardWidth
property var cardHeight: 100
property var cardHeight: Theme.fontSizeLarge * 6
property var cardWidth: ((dailyList.width + dailyList.spacing) / dailyList.visibleCount) - dailyList.spacing
property int initialIndex: 0
property bool dense: false

View File

@@ -437,21 +437,19 @@ Variants {
height: {
if (dock.isVertical) {
const extra = 4 + dock.borderThickness;
const hiddenHeight = Math.min(Math.max(dockBackground.implicitHeight + 64, 200), screenHeight * 0.5);
return dock.reveal ? Math.max(Math.min(dockBackground.implicitHeight + extra, maxDockHeight), hiddenHeight) : hiddenHeight;
} else {
return dock.reveal ? px(dock.effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockMargin) : 1;
if (!dock.reveal)
return Math.min(Math.max(dockBackground.height + 64, 200), screenHeight * 0.5);
return Math.min(dockBackground.height + 8 + dock.borderThickness, maxDockHeight);
}
return dock.reveal ? px(dock.effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockMargin) : 1;
}
width: {
if (dock.isVertical) {
return dock.reveal ? px(dock.effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockMargin) : 1;
} else {
const extra = 4 + dock.borderThickness;
const hiddenWidth = Math.min(Math.max(dockBackground.implicitWidth + 64, 200), screenWidth * 0.5);
return dock.reveal ? Math.max(Math.min(dockBackground.implicitWidth + extra, maxDockWidth), hiddenWidth) : hiddenWidth;
}
if (!dock.reveal)
return Math.min(Math.max(dockBackground.width + 64, 200), screenWidth * 0.5);
return Math.min(dockBackground.width + 8 + dock.borderThickness, maxDockWidth);
}
anchors {
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top) : undefined

View File

@@ -29,6 +29,15 @@ Item {
property bool showTooltip: mouseArea.containsMouse && !dragging
property var cachedDesktopEntry: null
property real actualIconSize: 40
property bool shouldShowIndicator: {
if (!appData)
return false;
if (appData.type === "window")
return true;
if (appData.type === "grouped")
return appData.windowCount > 0;
return appData.isRunning;
}
readonly property string coreIconColorOverride: SettingsData.dockLauncherLogoColorOverride
readonly property bool coreIconHasCustomColor: coreIconColorOverride !== "" && coreIconColorOverride !== "primary" && coreIconColorOverride !== "surface"
readonly property color effectiveCoreIconColor: {
@@ -206,7 +215,7 @@ Item {
anchors.fill: parent
hoverEnabled: true
enabled: true
preventStealing: true
preventStealing: dragging || longPressing
cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: mouse => {
@@ -295,7 +304,7 @@ Item {
groupedToplevel.activate();
} else if (contextMenu) {
const shouldHidePin = appData.appId === "org.quickshell";
contextMenu.showForButton(root, appData, root.height + 25, shouldHidePin, cachedDesktopEntry, parentDockScreen);
contextMenu.showForButton(root, appData, root.height + 25, shouldHidePin, cachedDesktopEntry, parentDockScreen, dockApps);
}
break;
}
@@ -342,7 +351,7 @@ Item {
case "grouped":
if (contextMenu) {
const shouldHidePin = appData.appId === "org.quickshell";
contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen);
contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen, dockApps);
}
break;
default:
@@ -365,7 +374,7 @@ Item {
if (!contextMenu)
return;
const shouldHidePin = appData.appId === "org.quickshell";
contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen);
contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen, dockApps);
}
}
}
@@ -498,15 +507,7 @@ Item {
sourceComponent: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? columnIndicator : rowIndicator
visible: {
if (!appData)
return false;
if (appData.type === "window")
return true;
if (appData.type === "grouped")
return appData.windowCount > 0;
return appData.isRunning;
}
visible: root.shouldShowIndicator
}
}

View File

@@ -17,40 +17,55 @@ Item {
property int draggedIndex: -1
property int dropTargetIndex: -1
property bool suppressShiftAnimation: false
property int maxVisibleApps: SettingsData.dockMaxVisibleApps
property int maxVisibleRunningApps: SettingsData.dockMaxVisibleRunningApps
property bool overflowExpanded: false
property int overflowItemCount: 0
readonly property real baseImplicitWidth: isVertical ? baseAppHeight : baseAppWidth
readonly property real baseImplicitHeight: isVertical ? baseAppWidth : baseAppHeight
readonly property real baseAppWidth: {
let count = 0;
const items = repeater.dockItems;
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.isInOverflow)
continue;
if (item.type === "separator") {
count += 8 / (iconSize * 1.2);
} else {
count += 1;
}
}
return count * (iconSize * 1.2) + Math.max(0, count - 1) * layoutFlow.spacing;
}
readonly property real baseAppHeight: iconSize
clip: false
implicitWidth: isVertical ? appLayout.height : appLayout.width
implicitHeight: isVertical ? appLayout.width : appLayout.height
function dockIndexToPinnedIndex(dockIndex) {
if (!SettingsData.dockLauncherEnabled) {
if (!SettingsData.dockLauncherEnabled)
return dockIndex;
}
const launcherPos = SessionData.dockLauncherPosition;
if (dockIndex < launcherPos) {
return dockIndex;
} else {
return dockIndex - 1;
}
return dockIndex < launcherPos ? dockIndex : dockIndex - 1;
}
function movePinnedApp(fromDockIndex, toDockIndex) {
const fromPinnedIndex = dockIndexToPinnedIndex(fromDockIndex);
const toPinnedIndex = dockIndexToPinnedIndex(toDockIndex);
if (fromPinnedIndex === toPinnedIndex) {
if (fromPinnedIndex === toPinnedIndex)
return;
}
const currentPinned = [...(SessionData.pinnedApps || [])];
if (fromPinnedIndex < 0 || fromPinnedIndex >= currentPinned.length || toPinnedIndex < 0 || toPinnedIndex >= currentPinned.length) {
if (fromPinnedIndex < 0 || fromPinnedIndex >= currentPinned.length || toPinnedIndex < 0 || toPinnedIndex >= currentPinned.length)
return;
}
const movedApp = currentPinned.splice(fromPinnedIndex, 1)[0];
currentPinned.splice(toPinnedIndex, 0, movedApp);
SessionData.setPinnedApps(currentPinned);
}
@@ -94,13 +109,10 @@ Item {
function getCoreAppData(appId) {
if (typeof AppSearchService === "undefined")
return null;
const coreApps = AppSearchService.coreApps || [];
for (let i = 0; i < coreApps.length; i++) {
const app = coreApps[i];
if (app.builtInPluginId === appId) {
return app;
}
if (coreApps[i].builtInPluginId === appId)
return coreApps[i];
}
return null;
}
@@ -108,21 +120,193 @@ Item {
function getCoreAppDataByTitle(windowTitle) {
if (typeof AppSearchService === "undefined" || !windowTitle)
return null;
const coreApps = AppSearchService.coreApps || [];
for (let i = 0; i < coreApps.length; i++) {
const app = coreApps[i];
if (app.name === windowTitle) {
return app;
}
if (coreApps[i].name === windowTitle)
return coreApps[i];
}
return null;
}
function buildBaseItems() {
const items = [];
const pinnedApps = [...(SessionData.pinnedApps || [])];
const allToplevels = CompositorService.sortedToplevels;
const sortedToplevels = (SettingsData.dockIsolateDisplays && root.dockScreen) ? allToplevels.filter(t => isOnScreen(t, root.dockScreen.name)) : allToplevels;
if (root.groupByApp) {
return buildGroupedItems(pinnedApps, sortedToplevels);
}
return buildUngroupedItems(pinnedApps, sortedToplevels);
}
function buildGroupedItems(pinnedApps, sortedToplevels) {
const items = [];
const appGroups = new Map();
pinnedApps.forEach(rawAppId => {
const appId = Paths.moddedAppId(rawAppId);
const coreAppData = getCoreAppData(appId);
appGroups.set(appId, {
appId: appId,
isPinned: true,
windows: [],
isCoreApp: coreAppData !== null,
coreAppData: coreAppData
});
});
sortedToplevels.forEach((toplevel, index) => {
const rawAppId = toplevel.appId || "unknown";
let appId = Paths.moddedAppId(rawAppId);
let coreAppData = null;
if (rawAppId === "org.quickshell") {
coreAppData = getCoreAppDataByTitle(toplevel.title);
if (coreAppData)
appId = coreAppData.builtInPluginId;
}
if (!appGroups.has(appId)) {
appGroups.set(appId, {
appId: appId,
isPinned: false,
windows: [],
isCoreApp: coreAppData !== null,
coreAppData: coreAppData
});
}
appGroups.get(appId).windows.push({
toplevel: toplevel,
index: index
});
});
const pinnedGroups = [];
const unpinnedGroups = [];
appGroups.forEach((group, appId) => {
const firstWindow = group.windows.length > 0 ? group.windows[0] : null;
const item = {
uniqueKey: "grouped_" + appId,
type: "grouped",
appId: appId,
toplevel: firstWindow ? firstWindow.toplevel : null,
isPinned: group.isPinned,
isRunning: group.windows.length > 0,
windowCount: group.windows.length,
allWindows: group.windows,
isCoreApp: group.isCoreApp || false,
coreAppData: group.coreAppData || null
};
(group.isPinned ? pinnedGroups : unpinnedGroups).push(item);
});
pinnedGroups.forEach(item => items.push(item));
insertLauncher(items);
if (pinnedGroups.length > 0 && unpinnedGroups.length > 0) {
items.push(createSeparator("separator_grouped"));
}
unpinnedGroups.forEach(item => items.push(item));
root.pinnedAppCount = pinnedGroups.length + (SettingsData.dockLauncherEnabled ? 1 : 0);
return {
items,
pinnedCount: pinnedGroups.length,
runningCount: unpinnedGroups.length
};
}
function buildUngroupedItems(pinnedApps, sortedToplevels) {
const items = [];
const runningAppIds = new Set();
const windowItems = [];
sortedToplevels.forEach((toplevel, index) => {
let uniqueKey = "window_" + index;
if (CompositorService.isHyprland && Hyprland.toplevels) {
const hyprlandToplevels = Array.from(Hyprland.toplevels.values);
for (let i = 0; i < hyprlandToplevels.length; i++) {
if (hyprlandToplevels[i].wayland === toplevel) {
uniqueKey = "window_" + hyprlandToplevels[i].address;
break;
}
}
}
const rawAppId = toplevel.appId || "unknown";
const moddedAppId = Paths.moddedAppId(rawAppId);
let coreAppData = null;
let isCoreApp = false;
if (rawAppId === "org.quickshell") {
coreAppData = getCoreAppDataByTitle(toplevel.title);
if (coreAppData)
isCoreApp = true;
}
const finalAppId = isCoreApp ? coreAppData.builtInPluginId : moddedAppId;
windowItems.push({
uniqueKey: uniqueKey,
type: "window",
appId: finalAppId,
toplevel: toplevel,
isPinned: false,
isRunning: true,
isCoreApp: isCoreApp,
coreAppData: coreAppData
});
runningAppIds.add(finalAppId);
});
const remainingWindowItems = windowItems.slice();
pinnedApps.forEach(rawAppId => {
const appId = Paths.moddedAppId(rawAppId);
const coreAppData = getCoreAppData(appId);
const matchIndex = remainingWindowItems.findIndex(item => item.appId === appId);
if (matchIndex !== -1) {
const windowItem = remainingWindowItems.splice(matchIndex, 1)[0];
windowItem.isPinned = true;
if (!windowItem.isCoreApp && coreAppData) {
windowItem.isCoreApp = true;
windowItem.coreAppData = coreAppData;
}
items.push(windowItem);
} else {
items.push({
uniqueKey: "pinned_" + appId,
type: "pinned",
appId: appId,
toplevel: null,
isPinned: true,
isRunning: runningAppIds.has(appId),
isCoreApp: coreAppData !== null,
coreAppData: coreAppData
});
}
});
root.pinnedAppCount = pinnedApps.length + (SettingsData.dockLauncherEnabled ? 1 : 0);
insertLauncher(items);
if (pinnedApps.length > 0 && remainingWindowItems.length > 0) {
items.push(createSeparator("separator_ungrouped"));
}
remainingWindowItems.forEach(item => items.push(item));
return {
items,
pinnedCount: pinnedApps.length,
runningCount: remainingWindowItems.length
};
}
function insertLauncher(targetArray) {
if (!SettingsData.dockLauncherEnabled)
return;
const launcherItem = {
uniqueKey: "launcher_button",
type: "launcher",
@@ -131,187 +315,154 @@ Item {
isPinned: true,
isRunning: false
};
const pos = Math.max(0, Math.min(SessionData.dockLauncherPosition, targetArray.length));
targetArray.splice(pos, 0, launcherItem);
}
function updateModel() {
const items = [];
const pinnedApps = [...(SessionData.pinnedApps || [])];
const allToplevels = CompositorService.sortedToplevels;
const sortedToplevels = (SettingsData.dockIsolateDisplays && root.dockScreen) ? allToplevels.filter(t => isOnScreen(t, root.dockScreen.name)) : allToplevels;
function createSeparator(key) {
return {
uniqueKey: key,
type: "separator",
appId: "__SEPARATOR__",
toplevel: null,
isPinned: false,
isRunning: false
};
}
if (root.groupByApp) {
const appGroups = new Map();
function markAsOverflow(item) {
return {
uniqueKey: item.uniqueKey,
type: item.type,
appId: item.appId,
toplevel: item.toplevel,
isPinned: item.isPinned,
isRunning: item.isRunning,
windowCount: item.windowCount,
allWindows: item.allWindows,
isCoreApp: item.isCoreApp,
coreAppData: item.coreAppData,
isInOverflow: true
};
}
pinnedApps.forEach(rawAppId => {
const appId = Paths.moddedAppId(rawAppId);
const coreAppData = getCoreAppData(appId);
appGroups.set(appId, {
appId: appId,
isPinned: true,
windows: [],
isCoreApp: coreAppData !== null,
coreAppData: coreAppData
});
});
function applyOverflow(baseResult) {
const {
items
} = baseResult;
const maxPinned = root.maxVisibleApps;
const maxRunning = root.maxVisibleRunningApps;
const hideRunning = maxRunning === 0;
sortedToplevels.forEach((toplevel, index) => {
const rawAppId = toplevel.appId || "unknown";
let appId = Paths.moddedAppId(rawAppId);
const pinnedItems = items.filter(i => (i.type === "pinned" || i.type === "grouped" || i.type === "window") && i.isPinned && i.appId !== "__LAUNCHER__");
const runningItems = hideRunning ? [] : items.filter(i => (i.type === "window" || i.type === "grouped") && i.isRunning && !i.isPinned);
let coreAppData = null;
if (rawAppId === "org.quickshell") {
coreAppData = getCoreAppDataByTitle(toplevel.title);
if (coreAppData) {
appId = coreAppData.builtInPluginId;
}
}
const pinnedOverflow = maxPinned > 0 && pinnedItems.length > maxPinned;
const runningOverflow = !hideRunning && maxRunning > 0 && runningItems.length > maxRunning;
if (!appGroups.has(appId)) {
appGroups.set(appId, {
appId: appId,
isPinned: false,
windows: [],
isCoreApp: coreAppData !== null,
coreAppData: coreAppData
});
}
appGroups.get(appId).windows.push({
toplevel: toplevel,
index: index
});
});
const pinnedGroups = [];
const unpinnedGroups = [];
Array.from(appGroups.entries()).forEach(([appId, group]) => {
const firstWindow = group.windows.length > 0 ? group.windows[0] : null;
const item = {
uniqueKey: "grouped_" + appId,
type: "grouped",
appId: appId,
toplevel: firstWindow ? firstWindow.toplevel : null,
isPinned: group.isPinned,
isRunning: group.windows.length > 0,
windowCount: group.windows.length,
allWindows: group.windows,
isCoreApp: group.isCoreApp || false,
coreAppData: group.coreAppData || null
};
if (group.isPinned) {
pinnedGroups.push(item);
} else {
unpinnedGroups.push(item);
}
});
pinnedGroups.forEach(item => items.push(item));
insertLauncher(items);
if (pinnedGroups.length > 0 && unpinnedGroups.length > 0) {
items.push({
uniqueKey: "separator_grouped",
type: "separator",
appId: "__SEPARATOR__",
toplevel: null,
isPinned: false,
isRunning: false
});
if (!pinnedOverflow && !runningOverflow) {
root.overflowItemCount = 0;
if (hideRunning) {
return items.filter(i => !((i.type === "window" || i.type === "grouped") && i.isRunning && !i.isPinned) && i.type !== "separator");
}
return items;
}
unpinnedGroups.forEach(item => items.push(item));
root.pinnedAppCount = pinnedGroups.length + (SettingsData.dockLauncherEnabled ? 1 : 0);
} else {
pinnedApps.forEach(rawAppId => {
const appId = Paths.moddedAppId(rawAppId);
const coreAppData = getCoreAppData(appId);
items.push({
uniqueKey: "pinned_" + appId,
type: "pinned",
appId: appId,
toplevel: null,
isPinned: true,
isRunning: false,
isCoreApp: coreAppData !== null,
coreAppData: coreAppData
});
});
const visiblePinnedKeys = new Set(pinnedOverflow ? pinnedItems.slice(0, maxPinned).map(i => i.uniqueKey) : pinnedItems.map(i => i.uniqueKey));
const visibleRunningKeys = new Set(runningOverflow ? runningItems.slice(0, maxRunning).map(i => i.uniqueKey) : runningItems.map(i => i.uniqueKey));
root.pinnedAppCount = pinnedApps.length + (SettingsData.dockLauncherEnabled ? 1 : 0);
const overflowPinnedCount = pinnedOverflow ? pinnedItems.length - maxPinned : 0;
const overflowRunningCount = runningOverflow ? runningItems.length - maxRunning : 0;
const totalOverflow = overflowPinnedCount + overflowRunningCount;
root.overflowItemCount = totalOverflow;
insertLauncher(items);
const finalItems = [];
let addedSeparator = false;
if (pinnedApps.length > 0 && sortedToplevels.length > 0) {
items.push({
uniqueKey: "separator_ungrouped",
type: "separator",
appId: "__SEPARATOR__",
toplevel: null,
isPinned: false,
isRunning: false
});
for (let i = 0; i < items.length; i++) {
const item = items[i];
switch (item.type) {
case "launcher":
finalItems.push(item);
break;
case "separator":
break;
case "pinned":
case "grouped":
case "window":
if (item.isPinned && item.appId !== "__LAUNCHER__") {
if (visiblePinnedKeys.has(item.uniqueKey)) {
finalItems.push(item);
} else {
finalItems.push(markAsOverflow(item));
}
} else if (item.isRunning && !item.isPinned) {
if (!addedSeparator && finalItems.length > 0) {
finalItems.push(createSeparator("separator_overflow"));
addedSeparator = true;
}
if (visibleRunningKeys.has(item.uniqueKey)) {
finalItems.push(item);
} else if (!hideRunning) {
finalItems.push(markAsOverflow(item));
}
}
break;
}
}
sortedToplevels.forEach((toplevel, index) => {
let uniqueKey = "window_" + index;
if (CompositorService.isHyprland && Hyprland.toplevels) {
const hyprlandToplevels = Array.from(Hyprland.toplevels.values);
for (let i = 0; i < hyprlandToplevels.length; i++) {
if (hyprlandToplevels[i].wayland === toplevel) {
uniqueKey = "window_" + hyprlandToplevels[i].address;
break;
}
}
}
const rawAppId = toplevel.appId || "unknown";
const moddedAppId = Paths.moddedAppId(rawAppId);
// Check if this is a core app window (e.g., Settings modal with appId "org.quickshell")
let coreAppData = null;
let isCoreApp = false;
if (rawAppId === "org.quickshell") {
coreAppData = getCoreAppDataByTitle(toplevel.title);
if (coreAppData) {
isCoreApp = true;
}
}
const finalAppId = isCoreApp ? coreAppData.builtInPluginId : moddedAppId;
const isPinned = pinnedApps.indexOf(finalAppId) !== -1;
items.push({
uniqueKey: uniqueKey,
type: "window",
appId: finalAppId,
toplevel: toplevel,
isPinned: isPinned,
isRunning: true,
isCoreApp: isCoreApp,
coreAppData: coreAppData
});
if (totalOverflow > 0) {
const toggleIndex = finalItems.findIndex(i => i.type === "separator");
const insertPos = toggleIndex >= 0 ? toggleIndex : finalItems.length;
finalItems.splice(insertPos, 0, {
uniqueKey: "overflow_toggle",
type: "overflow-toggle",
appId: "__OVERFLOW_TOGGLE__",
toplevel: null,
isPinned: false,
isRunning: false,
overflowCount: totalOverflow
});
}
dockItems = items;
return finalItems;
}
function updateModel() {
const baseResult = buildBaseItems();
dockItems = applyOverflow(baseResult);
}
delegate: Item {
id: delegateItem
property var dockButton: itemData.type === "launcher" ? launcherButton : button
property var itemData: modelData
readonly property bool isOverflowToggle: itemData.type === "overflow-toggle"
readonly property bool isInOverflow: itemData.isInOverflow === true
clip: false
z: (itemData.type === "launcher" ? launcherButton.dragging : button.dragging) ? 100 : 0
visible: !isInOverflow || root.overflowExpanded
opacity: (isInOverflow && !root.overflowExpanded) ? 0 : 1
scale: (isInOverflow && !root.overflowExpanded) ? 0.8 : 1
width: itemData.type === "separator" ? (root.isVertical ? root.iconSize : 8) : (root.isVertical ? root.iconSize : root.iconSize * 1.2)
height: itemData.type === "separator" ? (root.isVertical ? 8 : root.iconSize) : (root.isVertical ? root.iconSize * 1.2 : root.iconSize)
width: (isInOverflow && !root.overflowExpanded) ? 0 : (itemData.type === "separator" ? (root.isVertical ? root.iconSize : 8) : (root.isVertical ? root.iconSize : root.iconSize * 1.2))
height: (isInOverflow && !root.overflowExpanded) ? 0 : (itemData.type === "separator" ? (root.isVertical ? 8 : root.iconSize) : (root.isVertical ? root.iconSize * 1.2 : root.iconSize))
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
property real shiftOffset: {
if (root.draggedIndex < 0 || !itemData.isPinned || itemData.type === "separator")
@@ -363,34 +514,42 @@ Item {
anchors.centerIn: parent
}
DockOverflowButton {
id: overflowButton
visible: isOverflowToggle
anchors.centerIn: parent
width: delegateItem.width
height: delegateItem.height
actualIconSize: root.iconSize
overflowCount: itemData.overflowCount || 0
overflowExpanded: root.overflowExpanded
isVertical: root.isVertical
onClicked: root.overflowExpanded = !root.overflowExpanded
}
DockLauncherButton {
id: launcherButton
visible: itemData.type === "launcher"
anchors.centerIn: parent
width: delegateItem.width
height: delegateItem.height
actualIconSize: root.iconSize
dockApps: root
index: model.index
}
DockAppButton {
id: button
visible: itemData.type !== "separator" && itemData.type !== "launcher"
visible: !isOverflowToggle && itemData.type !== "separator" && itemData.type !== "launcher"
anchors.centerIn: parent
width: delegateItem.width
height: delegateItem.height
actualIconSize: root.iconSize
appData: itemData
contextMenu: root.contextMenu
dockApps: root
index: model.index
parentDockScreen: root.dockScreen
showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped"
windowTitle: {
const title = itemData?.toplevel?.title || "(Unnamed)";
@@ -420,10 +579,17 @@ Item {
root.suppressShiftAnimation = false;
});
}
function onDockLauncherPositionChanged() {
root.suppressShiftAnimation = true;
root.draggedIndex = -1;
root.dropTargetIndex = -1;
repeater.updateModel();
Qt.callLater(() => {
root.suppressShiftAnimation = false;
});
}
}
onGroupByAppChanged: repeater.updateModel()
Connections {
target: SettingsData
function onDockIsolateDisplaysChanged() {
@@ -438,18 +604,13 @@ Item {
root.suppressShiftAnimation = false;
});
}
}
Connections {
target: SessionData
function onDockLauncherPositionChanged() {
root.suppressShiftAnimation = true;
root.draggedIndex = -1;
root.dropTargetIndex = -1;
function onDockMaxVisibleAppsChanged() {
repeater.updateModel();
}
function onDockMaxVisibleRunningAppsChanged() {
repeater.updateModel();
Qt.callLater(() => {
root.suppressShiftAnimation = false;
});
}
}
onGroupByAppChanged: repeater.updateModel()
}

View File

@@ -1,7 +1,6 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Widgets
import qs.Common
import qs.Services
@@ -19,22 +18,24 @@ PanelWindow {
property bool hidePin: false
property var desktopEntry: null
property bool isDmsWindow: appData?.appId === "org.quickshell"
property var dockApps: null
function showForButton(button, data, dockHeight, hidePinOption, entry, dockScreen) {
function showForButton(button, data, dockHeight, hidePinOption, entry, dockScreen, parentDockApps) {
if (dockScreen) {
root.screen = dockScreen
root.screen = dockScreen;
}
anchorItem = button
appData = data
dockVisibleHeight = dockHeight || 40
hidePin = hidePinOption || false
desktopEntry = entry || null
anchorItem = button;
appData = data;
dockVisibleHeight = dockHeight || 40;
hidePin = hidePinOption || false;
desktopEntry = entry || null;
dockApps = parentDockApps || null;
visible = true
visible = true;
}
function close() {
visible = false
visible = false;
}
screen: null
@@ -55,110 +56,110 @@ PanelWindow {
onAnchorItemChanged: updatePosition()
onVisibleChanged: {
if (visible) {
updatePosition()
updatePosition();
}
}
function updatePosition() {
if (!anchorItem) {
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
return
anchorPos = Qt.point(screen.width / 2, screen.height - 100);
return;
}
const dockWindow = anchorItem.Window.window
const dockWindow = anchorItem.Window.window;
if (!dockWindow) {
anchorPos = Qt.point(screen.width / 2, screen.height - 100)
return
anchorPos = Qt.point(screen.width / 2, screen.height - 100);
return;
}
const buttonPosInDock = anchorItem.mapToItem(dockWindow.contentItem, 0, 0)
let actualDockHeight = root.dockVisibleHeight
const buttonPosInDock = anchorItem.mapToItem(dockWindow.contentItem, 0, 0);
let actualDockHeight = root.dockVisibleHeight;
function findDockBackground(item) {
if (item.objectName === "dockBackground") {
return item
return item;
}
for (var i = 0; i < item.children.length; i++) {
const found = findDockBackground(item.children[i])
const found = findDockBackground(item.children[i]);
if (found) {
return found
return found;
}
}
return null
return null;
}
const dockBackground = findDockBackground(dockWindow.contentItem)
let actualDockWidth = dockWindow.width
const dockBackground = findDockBackground(dockWindow.contentItem);
let actualDockWidth = dockWindow.width;
if (dockBackground) {
actualDockHeight = dockBackground.height
actualDockWidth = dockBackground.width
actualDockHeight = dockBackground.height;
actualDockWidth = dockBackground.width;
}
const isVertical = SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
const dockMargin = SettingsData.dockMargin + 16
let buttonScreenX, buttonScreenY
const isVertical = SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right;
const dockMargin = SettingsData.dockMargin + 16;
let buttonScreenX, buttonScreenY;
if (isVertical) {
const dockContentHeight = dockWindow.height
const screenHeight = root.screen.height
const dockTopMargin = Math.round((screenHeight - dockContentHeight) / 2)
buttonScreenY = dockTopMargin + buttonPosInDock.y + anchorItem.height / 2
const dockContentHeight = dockWindow.height;
const screenHeight = root.screen.height;
const dockTopMargin = Math.round((screenHeight - dockContentHeight) / 2);
buttonScreenY = dockTopMargin + buttonPosInDock.y + anchorItem.height / 2;
if (SettingsData.dockPosition === SettingsData.Position.Right) {
buttonScreenX = root.screen.width - actualDockWidth - dockMargin - 20
buttonScreenX = root.screen.width - actualDockWidth - dockMargin - 20;
} else {
buttonScreenX = actualDockWidth + dockMargin + 20
buttonScreenX = actualDockWidth + dockMargin + 20;
}
} else {
const isDockAtBottom = SettingsData.dockPosition === SettingsData.Position.Bottom
const isDockAtBottom = SettingsData.dockPosition === SettingsData.Position.Bottom;
if (isDockAtBottom) {
buttonScreenY = root.screen.height - actualDockHeight - dockMargin - 20
buttonScreenY = root.screen.height - actualDockHeight - dockMargin - 20;
} else {
buttonScreenY = actualDockHeight + dockMargin + 20
buttonScreenY = actualDockHeight + dockMargin + 20;
}
const dockContentWidth = dockWindow.width
const screenWidth = root.screen.width
const dockLeftMargin = Math.round((screenWidth - dockContentWidth) / 2)
buttonScreenX = dockLeftMargin + buttonPosInDock.x + anchorItem.width / 2
const dockContentWidth = dockWindow.width;
const screenWidth = root.screen.width;
const dockLeftMargin = Math.round((screenWidth - dockContentWidth) / 2);
buttonScreenX = dockLeftMargin + buttonPosInDock.x + anchorItem.width / 2;
}
anchorPos = Qt.point(buttonScreenX, buttonScreenY)
anchorPos = Qt.point(buttonScreenX, buttonScreenY);
}
Rectangle {
id: menuContainer
x: {
const isVertical = SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
const isVertical = SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right;
if (isVertical) {
const isDockAtRight = SettingsData.dockPosition === SettingsData.Position.Right
const isDockAtRight = SettingsData.dockPosition === SettingsData.Position.Right;
if (isDockAtRight) {
return Math.max(10, root.anchorPos.x - width + 30)
return Math.max(10, root.anchorPos.x - width + 30);
} else {
return Math.min(root.width - width - 10, root.anchorPos.x - 30)
return Math.min(root.width - width - 10, root.anchorPos.x - 30);
}
} else {
const left = 10
const right = root.width - width - 10
const want = root.anchorPos.x - width / 2
return Math.max(left, Math.min(right, want))
const left = 10;
const right = root.width - width - 10;
const want = root.anchorPos.x - width / 2;
return Math.max(left, Math.min(right, want));
}
}
y: {
const isVertical = SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
const isVertical = SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right;
if (isVertical) {
const top = 10
const bottom = root.height - height - 10
const want = root.anchorPos.y - height / 2
return Math.max(top, Math.min(bottom, want))
const top = 10;
const bottom = root.height - height - 10;
const want = root.anchorPos.y - height / 2;
return Math.max(top, Math.min(bottom, want));
} else {
const isDockAtBottom = SettingsData.dockPosition === SettingsData.Position.Bottom
const isDockAtBottom = SettingsData.dockPosition === SettingsData.Position.Bottom;
if (isDockAtBottom) {
return Math.max(10, root.anchorPos.y - height + 30)
return Math.max(10, root.anchorPos.y - height + 30);
} else {
return Math.min(root.height - height - 10, root.anchorPos.y - 30)
return Math.min(root.height - height - 10, root.anchorPos.y - 30);
}
}
}
@@ -202,17 +203,18 @@ PanelWindow {
// Window list for grouped apps
Repeater {
model: {
if (!root.appData || root.appData.type !== "grouped") return []
if (!root.appData || root.appData.type !== "grouped")
return [];
const toplevels = []
const allToplevels = ToplevelManager.toplevels.values
const toplevels = [];
const allToplevels = ToplevelManager.toplevels.values;
for (let i = 0; i < allToplevels.length; i++) {
const toplevel = allToplevels[i]
const toplevel = allToplevels[i];
if (toplevel.appId === root.appData.appId) {
toplevels.push(toplevel)
toplevels.push(toplevel);
}
}
return toplevels
return toplevels;
}
Rectangle {
@@ -227,7 +229,7 @@ PanelWindow {
anchors.right: closeButton.left
anchors.rightMargin: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
text: (modelData && modelData.title) ? modelData.title: I18n.tr("(Unnamed)")
text: (modelData && modelData.title) ? modelData.title : I18n.tr("(Unnamed)")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
@@ -259,9 +261,9 @@ PanelWindow {
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData && modelData.close) {
modelData.close()
modelData.close();
}
root.close()
root.close();
}
}
}
@@ -274,9 +276,9 @@ PanelWindow {
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData && modelData.activate) {
modelData.activate()
modelData.activate();
}
root.close()
root.close();
}
}
}
@@ -284,9 +286,11 @@ PanelWindow {
Rectangle {
visible: {
if (!root.appData) return false
if (root.appData.type !== "grouped") return false
return root.appData.windowCount > 0
if (!root.appData)
return false;
if (root.appData.type !== "grouped")
return false;
return root.appData.windowCount > 0;
}
width: parent.width
height: 1
@@ -343,9 +347,9 @@ PanelWindow {
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData) {
SessionService.launchDesktopAction(root.desktopEntry, modelData)
SessionService.launchDesktopAction(root.desktopEntry, modelData);
}
root.close()
root.close();
}
}
}
@@ -354,9 +358,9 @@ PanelWindow {
Rectangle {
visible: {
if (!root.desktopEntry?.actions || root.desktopEntry.actions.length === 0) {
return false
return false;
}
return !root.hidePin || (!root.isDmsWindow && root.desktopEntry && SessionService.nvidiaCommand)
return !root.hidePin || (!root.isDmsWindow && root.desktopEntry && SessionService.nvidiaCommand);
}
width: parent.width
height: 1
@@ -390,26 +394,26 @@ PanelWindow {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!root.appData) {
return
}
if (!root.appData)
return;
if (root.appData.isPinned) {
SessionData.removePinnedApp(root.appData.appId)
SessionData.removePinnedApp(root.appData.appId);
} else {
SessionData.addPinnedApp(root.appData.appId)
SessionData.addPinnedApp(root.appData.appId);
}
root.close()
root.close();
}
}
}
Rectangle {
visible: {
const hasNvidia = !root.isDmsWindow && root.desktopEntry && SessionService.nvidiaCommand
const hasWindow = root.appData && (root.appData.type === "window" || (root.appData.type === "grouped" && root.appData.windowCount > 0))
const hasPinOption = !root.hidePin
const hasContentAbove = hasPinOption || hasNvidia
return hasContentAbove && hasWindow
const hasNvidia = !root.isDmsWindow && root.desktopEntry && SessionService.nvidiaCommand;
const hasWindow = root.appData && (root.appData.type === "window" || (root.appData.type === "grouped" && root.appData.windowCount > 0));
const hasPinOption = !root.hidePin;
const hasContentAbove = hasPinOption || hasNvidia;
return hasContentAbove && hasWindow;
}
width: parent.width
height: 1
@@ -444,9 +448,9 @@ PanelWindow {
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.desktopEntry) {
SessionService.launchDesktopEntry(root.desktopEntry, true)
SessionService.launchDesktopEntry(root.desktopEntry, true);
}
root.close()
root.close();
}
}
}
@@ -466,9 +470,9 @@ PanelWindow {
anchors.verticalCenter: parent.verticalCenter
text: {
if (root.appData && root.appData.type === "grouped") {
return "Close All Windows"
return "Close All Windows";
}
return "Close Window"
return "Close Window";
}
font.pixelSize: Theme.fontSizeSmall
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
@@ -484,11 +488,11 @@ PanelWindow {
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.appData?.type === "window") {
root.appData?.toplevel?.close()
root.appData?.toplevel?.close();
} else if (root.appData?.type === "grouped") {
root.appData?.allWindows?.forEach(window => window.toplevel?.close())
root.appData?.allWindows?.forEach(window => window.toplevel?.close());
}
root.close()
root.close();
}
}
}

View File

@@ -115,7 +115,7 @@ Item {
anchors.fill: parent
hoverEnabled: true
enabled: true
preventStealing: true
preventStealing: dragging || longPressing
cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: mouse => {

View File

@@ -0,0 +1,75 @@
import QtQuick
import qs.Common
import qs.Widgets
Item {
id: root
property real actualIconSize: 40
property int overflowCount: 0
property bool overflowExpanded: false
property bool isVertical: false
signal clicked
Rectangle {
id: buttonBackground
anchors.centerIn: parent
width: actualIconSize
height: actualIconSize
radius: Theme.cornerRadius
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, mouseArea.containsMouse ? 0.2 : 0.1)
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
DankIcon {
id: arrowIcon
anchors.centerIn: parent
size: actualIconSize * 0.6
name: "expand_more"
color: Theme.surfaceText
rotation: isVertical ? (overflowExpanded ? 180 : 0) : (overflowExpanded ? 90 : -90)
Behavior on rotation {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
}
}
Rectangle {
visible: overflowCount > 0 && !overflowExpanded && SettingsData.dockShowOverflowBadge
anchors.right: buttonBackground.right
anchors.top: buttonBackground.top
anchors.rightMargin: -4
anchors.topMargin: -4
width: Math.max(18, badgeText.width + 8)
height: 18
radius: 9
color: Theme.primary
z: 10
StyledText {
id: badgeText
anchors.centerIn: parent
text: `+${overflowCount}`
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: Theme.onPrimary
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.clicked()
}
}

View File

@@ -20,6 +20,7 @@ Singleton {
property string matugenScheme: "scheme-tonal-spot"
property bool use24HourClock: true
property bool showSeconds: false
property bool padHours12Hour: false
property bool useFahrenheit: false
property bool nightModeEnabled: false
property string weatherLocation: "New York, NY"
@@ -39,6 +40,7 @@ Singleton {
property string widgetBackgroundColor: "sch"
property string lockDateFormat: ""
property bool lockScreenShowPowerActions: true
property bool lockScreenShowProfileImage: true
property var screenPreferences: ({})
property int animationSpeed: 2
property string wallpaperFillMode: "Fill"
@@ -52,6 +54,7 @@ Singleton {
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot";
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true;
showSeconds = settings.showSeconds !== undefined ? settings.showSeconds : false;
padHours12Hour = settings.padHours12Hour !== undefined ? settings.padHours12Hour : false;
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false;
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false;
weatherLocation = settings.weatherLocation !== undefined ? settings.weatherLocation : "New York, NY";
@@ -71,6 +74,7 @@ Singleton {
widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch";
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : "";
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true;
lockScreenShowProfileImage = settings.lockScreenShowProfileImage !== undefined ? settings.lockScreenShowProfileImage : true;
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({});
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : 2;
wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill";
@@ -88,6 +92,14 @@ Singleton {
}
}
function getEffectiveTimeFormat() {
if (use24HourClock)
return showSeconds ? "hh:mm:ss" : "hh:mm";
if (padHours12Hour)
return showSeconds ? "hh:mm:ss AP" : "hh:mm AP";
return showSeconds ? "h:mm:ss AP" : "h:mm AP";
}
function getEffectiveLockDateFormat() {
return lockDateFormat && lockDateFormat.length > 0 ? lockDateFormat : Locale.LongFormat;
}

View File

@@ -23,7 +23,6 @@ Item {
readonly property string xdgDataDirs: Quickshell.env("XDG_DATA_DIRS")
property string screenName: ""
property string randomFact: ""
property string hyprlandCurrentLayout: ""
property string hyprlandKeyboard: ""
property int hyprlandLayoutCount: 0
@@ -31,10 +30,6 @@ Item {
signal launchRequested
function pickRandomFact() {
randomFact = Facts.getRandomFact();
}
property bool weatherInitialized: false
function initWeatherService() {
@@ -58,7 +53,6 @@ Item {
}
Component.onCompleted: {
pickRandomFact();
initWeatherService();
if (isPrimaryScreen)
@@ -223,7 +217,7 @@ Item {
spacing: 0
property string fullTimeStr: {
const format = GreetdSettings.use24HourClock ? (GreetdSettings.showSeconds ? "HH:mm:ss" : "HH:mm") : (GreetdSettings.showSeconds ? "h:mm:ss AP" : "h:mm AP");
const format = GreetdSettings.getEffectiveTimeFormat();
return systemClock.date.toLocaleTimeString(Qt.locale(), format);
}
property var timeParts: fullTimeStr.split(':')
@@ -369,6 +363,7 @@ Item {
return PortalService.profileImage;
}
fallbackIcon: "person"
visible: GreetdSettings.lockScreenShowProfileImage
}
Rectangle {
@@ -961,20 +956,6 @@ Item {
}
}
StyledText {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Theme.spacingL
width: Math.min(parent.width - Theme.spacingXL * 2, implicitWidth)
text: root.randomFact
font.pixelSize: Theme.fontSizeSmall
color: "white"
opacity: 0.8
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.NoWrap
visible: root.randomFact !== ""
}
DankActionButton {
anchors.bottom: parent.bottom
anchors.left: parent.left

View File

@@ -65,7 +65,7 @@ Scope {
lockInitiatedLocally = true;
lockPowerOffArmed = SettingsData.lockScreenPowerOffMonitorsOnLock;
if (!SessionService.active && SessionService.loginctlAvailable) {
if (!SessionService.active && SessionService.loginctlAvailable && SettingsData.loginctlLockIntegration) {
pendingLock = true;
notifyLoginctl(true);
return;
@@ -99,7 +99,7 @@ Scope {
function onSessionLocked() {
if (shouldLock || pendingLock)
return;
if (!SessionService.active && SessionService.loginctlAvailable) {
if (!SessionService.active && SessionService.loginctlAvailable && SettingsData.loginctlLockIntegration) {
pendingLock = true;
lockInitiatedLocally = false;
return;

View File

@@ -25,7 +25,6 @@ Item {
property string screenName: ""
property bool unlocking: false
property string pamState: ""
property string randomFact: ""
property string hyprlandCurrentLayout: ""
property string hyprlandKeyboard: ""
property int hyprlandLayoutCount: 0
@@ -41,15 +40,7 @@ Item {
pamState = "";
}
function pickRandomFact() {
randomFact = Facts.getRandomFact();
}
Component.onCompleted: {
if (demoMode) {
pickRandomFact();
}
WeatherService.addRef();
UserInfoService.getUserInfo();
@@ -61,11 +52,6 @@ Item {
lockerReadyArmed = true;
}
onDemoModeChanged: {
if (demoMode) {
pickRandomFact();
}
}
Component.onDestruction: {
WeatherService.removeRef();
if (CompositorService.isHyprland) {
@@ -1195,12 +1181,12 @@ Item {
height: 24
color: Qt.rgba(255, 255, 255, 0.2)
anchors.verticalCenter: parent.verticalCenter
visible: MprisController.activePlayer
visible: MprisController.activePlayer && SettingsData.lockScreenShowMediaPlayer
}
Row {
spacing: Theme.spacingS
visible: MprisController.activePlayer
visible: MprisController.activePlayer && SettingsData.lockScreenShowMediaPlayer
anchors.verticalCenter: parent.verticalCenter
Item {
@@ -1369,7 +1355,7 @@ Item {
height: 24
color: Qt.rgba(255, 255, 255, 0.2)
anchors.verticalCenter: parent.verticalCenter
visible: MprisController.activePlayer && WeatherService.weather.available
visible: MprisController.activePlayer && SettingsData.lockScreenShowMediaPlayer && WeatherService.weather.available
}
Row {
@@ -1606,20 +1592,6 @@ Item {
}
}
}
StyledText {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Theme.spacingL
width: Math.min(parent.width - Theme.spacingXL * 2, implicitWidth)
text: root.randomFact
font.pixelSize: Theme.fontSizeSmall
color: "white"
opacity: 0.8
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.NoWrap
visible: root.randomFact !== ""
}
}
Pam {

View File

@@ -42,8 +42,6 @@ DankPopout {
if (!shouldBeVisible) {
searchText = "";
expandedPid = "";
if (processesView)
processesView.reset();
}
}
@@ -108,8 +106,11 @@ DankPopout {
Connections {
target: processListPopout
function onShouldBeVisibleChanged() {
if (processListPopout.shouldBeVisible)
if (processListPopout.shouldBeVisible) {
Qt.callLater(() => processListContent.forceActiveFocus());
} else {
processesView.reset();
}
}
}

View File

@@ -73,6 +73,17 @@ Column {
onToggled: checked => root.updateConfig("showAnalogSeconds", checked)
}
SettingsDivider {
visible: cfg.style === "digital" || cfg.style === "stacked"
}
SettingsToggleRow {
visible: cfg.style === "digital" || cfg.style === "stacked"
text: I18n.tr("Show Seconds")
checked: cfg.showDigitalSeconds ?? false
onToggled: checked => root.updateConfig("showDigitalSeconds", checked)
}
SettingsDivider {}
SettingsToggleRow {

View File

@@ -162,6 +162,39 @@ Item {
}
}
}
SettingsSliderRow {
settingKey: "dockMaxVisibleApps"
tags: ["dock", "overflow", "max", "apps", "limit"]
text: I18n.tr("Max Pinned Apps (0 = Unlimited)")
minimum: 0
maximum: 30
value: SettingsData.dockMaxVisibleApps
defaultValue: 0
unit: ""
onSliderValueChanged: newValue => SettingsData.set("dockMaxVisibleApps", newValue)
}
SettingsSliderRow {
settingKey: "dockMaxVisibleRunningApps"
tags: ["dock", "overflow", "max", "running", "apps", "limit"]
text: I18n.tr("Max Running Apps (0 = Unlimited)")
minimum: 0
maximum: 30
value: SettingsData.dockMaxVisibleRunningApps
defaultValue: 0
unit: ""
onSliderValueChanged: newValue => SettingsData.set("dockMaxVisibleRunningApps", newValue)
}
SettingsToggleRow {
settingKey: "dockShowOverflowBadge"
tags: ["dock", "overflow", "badge", "count", "indicator"]
text: I18n.tr("Show Overflow Badge Count")
description: I18n.tr("Displays count when overflow is active")
checked: SettingsData.dockShowOverflowBadge
onToggled: checked => SettingsData.set("dockShowOverflowBadge", checked)
}
}
SettingsCard {
@@ -174,7 +207,6 @@ Item {
settingKey: "dockLauncherEnabled"
tags: ["dock", "launcher", "button", "apps"]
text: I18n.tr("Show Launcher Button")
description: I18n.tr("Add a draggable launcher button to the dock")
checked: SettingsData.dockLauncherEnabled
onToggled: checked => SettingsData.set("dockLauncherEnabled", checked)
}
@@ -184,20 +216,12 @@ Item {
spacing: Theme.spacingL
visible: SettingsData.dockLauncherEnabled
StyledText {
width: parent.width
text: I18n.tr("Long press and drag the launcher button to reposition it in the dock")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
Column {
width: parent.width
spacing: Theme.spacingM
StyledText {
text: I18n.tr("Launcher Icon")
text: I18n.tr("Icon")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium

View File

@@ -722,7 +722,7 @@ Item {
anchors.centerIn: parent
text: "DMS"
font.pixelSize: Theme.fontSizeSmall - 2
color: Theme.primaryText
color: Theme.primary
}
}
}

View File

@@ -76,6 +76,14 @@ Item {
onToggled: checked => SettingsData.set("lockScreenShowPasswordField", checked)
}
SettingsToggleRow {
settingKey: "lockScreenShowMediaPlayer"
tags: ["lock", "screen", "media", "player", "music", "mpris"]
text: I18n.tr("Show Media Player", "Enable media player controls on the lock screen window")
checked: SettingsData.lockScreenShowMediaPlayer
onToggled: checked => SettingsData.set("lockScreenShowMediaPlayer", checked)
}
SettingsDropdownRow {
settingKey: "lockScreenNotificationMode"
tags: ["lock", "screen", "notification", "notifications", "privacy"]

View File

@@ -125,6 +125,19 @@ Item {
return Theme.warning;
}
function formatThemeAutoTime(isoString) {
if (!isoString)
return "";
try {
const date = new Date(isoString);
if (isNaN(date.getTime()))
return "";
return date.toLocaleTimeString(Qt.locale(), "HH:mm");
} catch (e) {
return "";
}
}
Component.onCompleted: {
SettingsData.detectAvailableIconThemes();
SettingsData.detectAvailableCursorThemes();
@@ -151,9 +164,7 @@ Item {
detection[item.id] = item.detected;
}
themeColorsTab.templateDetection = detection;
} catch (e) {
console.warn("ThemeColorsTab: Failed to parse template check:", e);
}
} catch (e) {}
}
}
}
@@ -962,6 +973,439 @@ Item {
}
}
SettingsCard {
tab: "theme"
tags: ["automatic", "color", "mode", "schedule", "sunrise", "sunset"]
title: I18n.tr("Automatic Color Mode")
settingKey: "automaticColorMode"
iconName: "schedule"
Column {
width: parent.width
spacing: Theme.spacingM
DankToggle {
id: themeModeAutoToggle
width: parent.width
text: I18n.tr("Automatic Control")
checked: SessionData.themeModeAutoEnabled
onToggled: checked => {
SessionData.setThemeModeAutoEnabled(checked);
}
Connections {
target: SessionData
function onThemeModeAutoEnabledChanged() {
themeModeAutoToggle.checked = SessionData.themeModeAutoEnabled;
}
}
}
Column {
width: parent.width
spacing: Theme.spacingM
visible: SessionData.themeModeAutoEnabled
DankToggle {
width: parent.width
text: I18n.tr("Share Gamma Control Settings")
checked: SessionData.themeModeShareGammaSettings
onToggled: checked => {
SessionData.setThemeModeShareGammaSettings(checked);
}
}
Item {
width: parent.width
height: 45 + Theme.spacingM
DankTabBar {
id: themeModeTabBar
width: 200
height: 45
anchors.horizontalCenter: parent.horizontalCenter
model: [
{
"text": "Time",
"icon": "access_time"
},
{
"text": "Location",
"icon": "place"
}
]
Component.onCompleted: {
currentIndex = SessionData.themeModeAutoMode === "location" ? 1 : 0;
Qt.callLater(updateIndicator);
}
onTabClicked: index => {
SessionData.setThemeModeAutoMode(index === 1 ? "location" : "time");
currentIndex = index;
}
Connections {
target: SessionData
function onThemeModeAutoModeChanged() {
themeModeTabBar.currentIndex = SessionData.themeModeAutoMode === "location" ? 1 : 0;
Qt.callLater(themeModeTabBar.updateIndicator);
}
}
}
}
Column {
width: parent.width
spacing: Theme.spacingM
visible: SessionData.themeModeAutoMode === "time" && !SessionData.themeModeShareGammaSettings
Column {
spacing: Theme.spacingXS
anchors.horizontalCenter: parent.horizontalCenter
Row {
spacing: Theme.spacingM
StyledText {
text: ""
width: 50
height: 20
}
StyledText {
text: I18n.tr("Hour")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: 70
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: I18n.tr("Minute")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: 70
horizontalAlignment: Text.AlignHCenter
}
}
Row {
spacing: Theme.spacingM
StyledText {
text: I18n.tr("Start")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: 50
height: 40
verticalAlignment: Text.AlignVCenter
}
DankDropdown {
dropdownWidth: 70
currentValue: SessionData.themeModeStartHour.toString()
options: {
var hours = [];
for (var i = 0; i < 24; i++)
hours.push(i.toString());
return hours;
}
onValueChanged: value => {
SessionData.setThemeModeStartHour(parseInt(value));
}
}
DankDropdown {
dropdownWidth: 70
currentValue: SessionData.themeModeStartMinute.toString().padStart(2, '0')
options: {
var minutes = [];
for (var i = 0; i < 60; i += 5) {
minutes.push(i.toString().padStart(2, '0'));
}
return minutes;
}
onValueChanged: value => {
SessionData.setThemeModeStartMinute(parseInt(value));
}
}
}
Row {
spacing: Theme.spacingM
StyledText {
text: I18n.tr("End")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: 50
height: 40
verticalAlignment: Text.AlignVCenter
}
DankDropdown {
dropdownWidth: 70
currentValue: SessionData.themeModeEndHour.toString()
options: {
var hours = [];
for (var i = 0; i < 24; i++)
hours.push(i.toString());
return hours;
}
onValueChanged: value => {
SessionData.setThemeModeEndHour(parseInt(value));
}
}
DankDropdown {
dropdownWidth: 70
currentValue: SessionData.themeModeEndMinute.toString().padStart(2, '0')
options: {
var minutes = [];
for (var i = 0; i < 60; i += 5) {
minutes.push(i.toString().padStart(2, '0'));
}
return minutes;
}
onValueChanged: value => {
SessionData.setThemeModeEndMinute(parseInt(value));
}
}
}
}
}
Column {
width: parent.width
spacing: Theme.spacingM
visible: SessionData.themeModeAutoMode === "location" && !SessionData.themeModeShareGammaSettings
DankToggle {
id: themeModeIpLocationToggle
width: parent.width
text: I18n.tr("Use IP Location")
checked: SessionData.nightModeUseIPLocation || false
onToggled: checked => {
SessionData.setNightModeUseIPLocation(checked);
}
Connections {
target: SessionData
function onNightModeUseIPLocationChanged() {
themeModeIpLocationToggle.checked = SessionData.nightModeUseIPLocation;
}
}
}
Column {
width: parent.width
spacing: Theme.spacingM
visible: !SessionData.nightModeUseIPLocation
StyledText {
text: I18n.tr("Manual Coordinates")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
width: parent.width
}
Row {
spacing: Theme.spacingL
anchors.horizontalCenter: parent.horizontalCenter
Column {
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Latitude")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
width: 120
height: 40
text: SessionData.latitude.toString()
placeholderText: "0.0"
onEditingFinished: {
const lat = parseFloat(text);
if (!isNaN(lat) && lat >= -90 && lat <= 90 && lat !== SessionData.latitude) {
SessionData.setLatitude(lat);
}
}
}
}
Column {
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Longitude")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
width: 120
height: 40
text: SessionData.longitude.toString()
placeholderText: "0.0"
onEditingFinished: {
const lon = parseFloat(text);
if (!isNaN(lon) && lon >= -180 && lon <= 180 && lon !== SessionData.longitude) {
SessionData.setLongitude(lon);
}
}
}
}
}
StyledText {
text: I18n.tr("Uses sunrise/sunset times based on your location.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
}
StyledText {
width: parent.width
text: I18n.tr("Using shared settings from Gamma Control")
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
visible: SessionData.themeModeShareGammaSettings
}
Rectangle {
width: parent.width
height: statusRow.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
Row {
id: statusRow
anchors.centerIn: parent
spacing: Theme.spacingL
width: parent.width - Theme.spacingM * 2
Column {
spacing: 2
width: (parent.width - Theme.spacingL * 2) / 3
anchors.verticalCenter: parent.verticalCenter
Row {
spacing: Theme.spacingS
anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
width: 8
height: 8
radius: 4
color: SessionData.themeModeAutoEnabled ? Theme.success : Theme.error
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Automation")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
}
StyledText {
text: SessionData.themeModeAutoEnabled ? I18n.tr("Enabled") : I18n.tr("Disabled")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
width: parent.width
}
}
Column {
spacing: 2
width: (parent.width - Theme.spacingL * 2) / 3
anchors.verticalCenter: parent.verticalCenter
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingS
DankIcon {
name: SessionData.isLightMode ? "light_mode" : "dark_mode"
size: Theme.iconSize
color: SessionData.isLightMode ? "#FFA726" : "#7E57C2"
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: SessionData.isLightMode ? I18n.tr("Light Mode") : I18n.tr("Dark Mode")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: I18n.tr("Active")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
width: parent.width
}
}
Column {
spacing: 2
width: (parent.width - Theme.spacingL * 2) / 3
anchors.verticalCenter: parent.verticalCenter
visible: SessionData.themeModeAutoEnabled && SessionData.themeModeNextTransition
Row {
spacing: Theme.spacingS
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: "schedule"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Next Transition")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: themeColorsTab.formatThemeAutoTime(SessionData.themeModeNextTransition)
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
width: parent.width
}
}
}
}
}
}
}
SettingsCard {
tab: "theme"
tags: ["light", "dark", "mode", "appearance"]
@@ -1040,6 +1484,42 @@ Item {
}
}
SettingsDropdownRow {
tab: "theme"
tags: ["control", "center", "tile", "button", "color", "active"]
settingKey: "controlCenterTileColorMode"
text: I18n.tr("Control Center Tile Color")
description: I18n.tr("Active tile background and icon color")
options: ["Primary", "Primary Container", "Secondary", "Surface Variant"]
currentValue: {
switch (SettingsData.controlCenterTileColorMode) {
case "primaryContainer":
return "Primary Container";
case "secondary":
return "Secondary";
case "surfaceVariant":
return "Surface Variant";
default:
return "Primary";
}
}
onValueChanged: value => {
switch (value) {
case "Primary Container":
SettingsData.set("controlCenterTileColorMode", "primaryContainer");
return;
case "Secondary":
SettingsData.set("controlCenterTileColorMode", "secondary");
return;
case "Surface Variant":
SettingsData.set("controlCenterTileColorMode", "surfaceVariant");
return;
default:
SettingsData.set("controlCenterTileColorMode", "primary");
}
}
}
SettingsSliderRow {
tab: "theme"
tags: ["popup", "transparency", "opacity", "modal"]

View File

@@ -49,6 +49,17 @@ Item {
checked: SettingsData.showSeconds
onToggled: checked => SettingsData.set("showSeconds", checked)
}
SettingsToggleRow {
tab: "time"
tags: ["time", "12hour", "format", "padding", "leading", "zero"]
settingKey: "padHours12Hour"
text: I18n.tr("Pad Hours")
description: "02:31 PM vs 2:31 PM"
checked: SettingsData.padHours12Hour
onToggled: checked => SettingsData.set("padHours12Hour", checked)
visible: !SettingsData.use24HourClock
}
}
SettingsCard {

View File

@@ -68,6 +68,13 @@ Item {
"icon": "apps",
"enabled": true
},
{
"id": "appsDock",
"text": I18n.tr("Apps Dock"),
"description": I18n.tr("Pinned and running apps with drag-and-drop"),
"icon": "dock_to_bottom",
"enabled": true
},
{
"id": "clock",
"text": I18n.tr("Clock"),

View File

@@ -373,81 +373,42 @@ Column {
}
}
DankActionButton {
id: musicMenuButton
visible: modelData.id === "music"
buttonSize: 32
iconName: "more_vert"
iconSize: 18
iconColor: Theme.outline
onClicked: {
musicContextMenu.widgetData = modelData;
musicContextMenu.sectionId = root.sectionId;
musicContextMenu.widgetIndex = index;
var buttonPos = musicMenuButton.mapToItem(root, 0, 0);
var popupWidth = musicContextMenu.width;
var popupHeight = musicContextMenu.height;
var xPos = buttonPos.x - popupWidth - Theme.spacingS;
if (xPos < 0)
xPos = buttonPos.x + musicMenuButton.width + Theme.spacingS;
var yPos = buttonPos.y - popupHeight / 2 + musicMenuButton.height / 2;
if (yPos < 0) {
yPos = Theme.spacingS;
} else if (yPos + popupHeight > root.height) {
yPos = root.height - popupHeight - Theme.spacingS;
}
musicContextMenu.x = xPos;
musicContextMenu.y = yPos;
musicContextMenu.open();
}
}
Row {
spacing: Theme.spacingXS
visible: modelData.id === "clock" || modelData.id === "music" || modelData.id === "focusedWindow" || modelData.id === "runningApps" || modelData.id === "keyboard_layout_name"
DankActionButton {
id: smallSizeButton
buttonSize: 28
visible: modelData.id === "music"
iconName: "photo_size_select_small"
iconSize: 16
iconColor: (modelData.mediaSize !== undefined ? modelData.mediaSize : SettingsData.mediaSize) === 0 ? Theme.primary : Theme.outline
onClicked: {
root.compactModeChanged("music", 0);
}
onEntered: {
sharedTooltip.show("Small", smallSizeButton, 0, 0, "bottom");
}
onExited: {
sharedTooltip.hide();
}
}
DankActionButton {
id: mediumSizeButton
buttonSize: 28
visible: modelData.id === "music"
iconName: "photo_size_select_actual"
iconSize: 16
iconColor: (modelData.mediaSize !== undefined ? modelData.mediaSize : SettingsData.mediaSize) === 1 ? Theme.primary : Theme.outline
onClicked: {
root.compactModeChanged("music", 1);
}
onEntered: {
sharedTooltip.show("Medium", mediumSizeButton, 0, 0, "bottom");
}
onExited: {
sharedTooltip.hide();
}
}
DankActionButton {
id: largeSizeButton
buttonSize: 28
visible: modelData.id === "music"
iconName: "photo_size_select_large"
iconSize: 16
iconColor: (modelData.mediaSize !== undefined ? modelData.mediaSize : SettingsData.mediaSize) === 2 ? Theme.primary : Theme.outline
onClicked: {
root.compactModeChanged("music", 2);
}
onEntered: {
sharedTooltip.show("Large", largeSizeButton, 0, 0, "bottom");
}
onExited: {
sharedTooltip.hide();
}
}
DankActionButton {
id: largerSizeButton
buttonSize: 28
visible: modelData.id === "music"
iconName: "fit_screen"
iconSize: 16
iconColor: (modelData.mediaSize !== undefined ? modelData.mediaSize : SettingsData.mediaSize) === 3 ? Theme.primary : Theme.outline
onClicked: {
root.compactModeChanged("music", 3);
}
onEntered: {
sharedTooltip.show("Largest", largerSizeButton, 0, 0, "bottom");
}
onExited: {
sharedTooltip.hide();
}
}
visible: modelData.id === "clock" || modelData.id === "focusedWindow" || modelData.id === "runningApps" || modelData.id === "keyboard_layout_name"
DankActionButton {
id: compactModeButton
@@ -1308,4 +1269,119 @@ Column {
}
}
}
Popup {
id: musicContextMenu
property var widgetData: null
property string sectionId: ""
property int widgetIndex: -1
width: 180
height: musicMenuColumn.implicitHeight + Theme.spacingS * 2
padding: 0
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
background: Rectangle {
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
}
contentItem: Item {
Column {
id: musicMenuColumn
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: 2
Repeater {
model: [
{
icon: "photo_size_select_small",
label: I18n.tr("Small"),
sizeValue: 0
},
{
icon: "photo_size_select_actual",
label: I18n.tr("Medium"),
sizeValue: 1
},
{
icon: "photo_size_select_large",
label: I18n.tr("Large"),
sizeValue: 2
},
{
icon: "fit_screen",
label: I18n.tr("Largest"),
sizeValue: 3
}
]
delegate: Rectangle {
required property var modelData
required property int index
function isSelected() {
var wd = musicContextMenu.widgetData;
var currentSize = wd?.mediaSize ?? SettingsData.mediaSize;
return currentSize === modelData.sizeValue;
}
width: musicMenuColumn.width
height: Math.max(18, Theme.fontSizeSmall) + Theme.spacingM * 2
radius: Theme.cornerRadius
color: musicOptionArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: modelData.icon
size: 18
color: isSelected() ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: modelData.label
font.pixelSize: Theme.fontSizeSmall
font.weight: isSelected() ? Font.Medium : Font.Normal
color: isSelected() ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
DankIcon {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
name: "check"
size: 16
color: Theme.primary
visible: isSelected()
}
MouseArea {
id: musicOptionArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.compactModeChanged("music", modelData.sizeValue);
musicContextMenu.close();
}
}
}
}
}
}
}
}

View File

@@ -104,6 +104,17 @@ Item {
}
}
SettingsSliderRow {
visible: SettingsData.showWorkspaceApps
text: I18n.tr("Icon Size")
value: SettingsData.workspaceAppIconSizeOffset
minimum: 0
maximum: 10
unit: "px"
defaultValue: 0
onSliderValueChanged: newValue => SettingsData.set("workspaceAppIconSizeOffset", newValue)
}
SettingsToggleRow {
settingKey: "groupWorkspaceApps"
tags: ["workspace", "apps", "icons", "group", "grouped", "collapse"]

View File

@@ -219,7 +219,8 @@ Singleton {
action: plugin.action,
categories: plugin.categories,
isCore: true,
builtInPluginId: pluginId
builtInPluginId: pluginId,
cornerIcon: plugin.cornerIcon
});
}
return apps;
@@ -854,6 +855,21 @@ Singleton {
return false;
}
function getPluginPasteText(pluginId, item) {
if (typeof PluginService === "undefined")
return null;
const instance = PluginService.pluginInstances[pluginId];
if (!instance)
return null;
if (typeof instance.getPasteText === "function") {
return instance.getPasteText(item);
}
return null;
}
function searchPluginItems(query) {
if (typeof PluginService === "undefined")
return [];
@@ -868,4 +884,52 @@ Singleton {
return allItems;
}
function getPluginLauncherCategories(pluginId) {
if (typeof PluginService === "undefined")
return [];
const instance = PluginService.pluginInstances[pluginId];
if (!instance)
return [];
if (typeof instance.getCategories !== "function")
return [];
try {
return instance.getCategories() || [];
} catch (e) {
console.warn("AppSearchService: Error getting categories from plugin", pluginId, ":", e);
return [];
}
}
function setPluginLauncherCategory(pluginId, categoryId) {
if (typeof PluginService === "undefined")
return;
const instance = PluginService.pluginInstances[pluginId];
if (!instance)
return;
if (typeof instance.setCategory !== "function")
return;
try {
instance.setCategory(categoryId);
} catch (e) {
console.warn("AppSearchService: Error setting category on plugin", pluginId, ":", e);
}
}
function pluginHasCategories(pluginId) {
if (typeof PluginService === "undefined")
return false;
const instance = PluginService.pluginInstances[pluginId];
if (!instance)
return false;
return typeof instance.getCategories === "function";
}
}

View File

@@ -130,7 +130,7 @@ Singleton {
Component.onCompleted: {
root.userPreference = SettingsData.networkPreference;
lastConnectedVpnUuid = SettingsData.vpnLastConnected || "";
lastConnectedVpnUuid = SessionData.vpnLastConnected || "";
if (socketPath && socketPath.length > 0) {
checkDMSCapabilities();
}
@@ -293,7 +293,7 @@ Singleton {
if (vpnConnected && activeUuid) {
lastConnectedVpnUuid = activeUuid;
SettingsData.set("vpnLastConnected", activeUuid);
SessionData.setVpnLastConnected(activeUuid);
}
if (vpnIsBusy) {

View File

@@ -56,6 +56,7 @@ Singleton {
signal wlrOutputStateUpdate(var data)
signal evdevStateUpdate(var data)
signal gammaStateUpdate(var data)
signal themeAutoStateUpdate(var data)
signal openUrlRequested(string url)
signal appPickerRequested(var data)
signal screensaverStateUpdate(var data)
@@ -64,7 +65,7 @@ Singleton {
property bool screensaverInhibited: false
property var screensaverInhibitors: []
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser", "dbus"]
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser", "dbus"]
Component.onCompleted: {
if (socketPath && socketPath.length > 0) {
@@ -304,7 +305,7 @@ Singleton {
excludeServices = [excludeServices];
}
const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "cups", "dwl", "brightness", "extworkspace", "browser", "dbus"];
const allServices = ["network", "loginctl", "freedesktop", "gamma", "theme.auto", "bluetooth", "cups", "dwl", "brightness", "extworkspace", "browser", "dbus"];
const filtered = allServices.filter(s => !excludeServices.includes(s));
subscribe(filtered);
}
@@ -373,6 +374,8 @@ Singleton {
evdevStateUpdate(data);
} else if (service === "gamma") {
gammaStateUpdate(data);
} else if (service === "theme.auto") {
themeAutoStateUpdate(data);
} else if (service === "browser.open_requested") {
if (data.target) {
if (data.requestType === "url" || !data.requestType) {
@@ -737,4 +740,10 @@ Singleton {
if (callback) callback(response);
});
}
function renameWorkspace(name, callback) {
sendRequest("extworkspace.renameWorkspace", {
"name": name
}, callback);
}
}

View File

@@ -88,8 +88,8 @@ Singleton {
if (transform !== 0)
monitorLine += ", transform, " + transform;
if (output.vrr_supported && output.vrr_enabled)
monitorLine += ", vrr, 1";
if (output.vrr_supported)
monitorLine += ", vrr, " + (output.vrr_enabled ? "1" : "0");
if (output.mirror && output.mirror.length > 0)
monitorLine += ", mirror, " + output.mirror;
@@ -308,4 +308,18 @@ decoration {
reloadConfig();
});
}
function renameWorkspace(newName) {
if (!Hyprland.focusedWorkspace)
return;
const wsId = Hyprland.focusedWorkspace.id;
if (!wsId)
return;
const fullName = wsId + " " + newName;
Proc.runCommand("hyprland-rename-ws", ["hyprctl", "dispatch", "renameworkspace", String(wsId), fullName], (output, exitCode) => {
if (exitCode !== 0) {
console.warn("HyprlandService: Failed to rename workspace:", output);
}
});
}
}

View File

@@ -1422,6 +1422,17 @@ Singleton {
return block;
}
function renameWorkspace(name) {
return send({
"Action": {
"SetWorkspaceName": {
"name": name,
"workspace": null
}
}
});
}
IpcHandler {
function screenshot(): string {
if (!CompositorService.isNiri) {

View File

@@ -592,6 +592,13 @@ Singleton {
return SettingsData.getPluginSetting(pluginId, key, defaultValue);
}
function getPluginPath(pluginId) {
const plugin = availablePlugins[pluginId];
if (!plugin)
return "";
return plugin.pluginDirectory || "";
}
function saveAllPluginSettings() {
SettingsData.savePluginSettings();
}

View File

@@ -416,6 +416,17 @@ Singleton {
}
}
function toggleDankLauncherV2WithQuery(query: string) {
if (dankLauncherV2Modal) {
dankLauncherV2Modal.toggleWithQuery(query);
} else if (dankLauncherV2ModalLoader) {
_dankLauncherV2PendingQuery = query;
_dankLauncherV2WantsOpen = true;
_dankLauncherV2WantsToggle = false;
dankLauncherV2ModalLoader.active = true;
}
}
function _onDankLauncherV2ModalLoaded() {
if (_dankLauncherV2WantsOpen) {
_dankLauncherV2WantsOpen = false;

View File

@@ -29,6 +29,7 @@ Singleton {
}
property bool loginctlAvailable: false
property bool wtypeAvailable: false
property string sessionId: ""
property string sessionPath: ""
property bool locked: false
@@ -59,6 +60,7 @@ Singleton {
detectElogindProcess.running = true;
detectHibernateProcess.running = true;
detectPrimeRunProcess.running = true;
detectWtypeProcess.running = true;
console.info("SessionService: Native inhibitor available:", nativeInhibitorAvailable);
if (!SettingsData.loginctlLockIntegration) {
console.log("SessionService: loginctl lock integration disabled by user");
@@ -124,6 +126,15 @@ Singleton {
}
}
Process {
id: detectWtypeProcess
running: false
command: ["which", "wtype"]
onExited: exitCode => {
wtypeAvailable = (exitCode === 0);
}
}
Process {
id: detectPrimeRunProcess
running: false

View File

@@ -1,13 +1,21 @@
import QtQuick
import qs.Common
Image {
Item {
id: root
property string imagePath: ""
property int maxCacheSize: 512
property int status: isAnimated ? animatedImg.status : staticImg.status
property int fillMode: Image.PreserveAspectCrop
readonly property bool isRemoteUrl: imagePath.startsWith("http://") || imagePath.startsWith("https://")
readonly property bool isAnimated: {
if (!imagePath)
return false;
const lower = imagePath.toLowerCase();
return lower.endsWith(".gif") || lower.endsWith(".webp");
}
readonly property string normalizedPath: {
if (!imagePath)
return "";
@@ -30,7 +38,7 @@ Image {
}
readonly property string imageHash: normalizedPath ? djb2Hash(normalizedPath) : ""
readonly property string cachePath: imageHash && !isRemoteUrl ? `${Paths.stringify(Paths.imagecache)}/${imageHash}@${maxCacheSize}x${maxCacheSize}.png` : ""
readonly property string cachePath: imageHash && !isRemoteUrl && !isAnimated ? `${Paths.stringify(Paths.imagecache)}/${imageHash}@${maxCacheSize}x${maxCacheSize}.png` : ""
readonly property string encodedImagePath: {
if (!normalizedPath)
return "";
@@ -39,39 +47,56 @@ Image {
return "file://" + normalizedPath.split('/').map(s => encodeURIComponent(s)).join('/');
}
asynchronous: true
fillMode: Image.PreserveAspectCrop
sourceSize.width: maxCacheSize
sourceSize.height: maxCacheSize
smooth: true
AnimatedImage {
id: animatedImg
anchors.fill: parent
visible: root.isAnimated
asynchronous: true
fillMode: root.fillMode
source: root.isAnimated ? root.imagePath : ""
playing: visible && status === AnimatedImage.Ready
}
Image {
id: staticImg
anchors.fill: parent
visible: !root.isAnimated
asynchronous: true
fillMode: root.fillMode
sourceSize.width: root.maxCacheSize
sourceSize.height: root.maxCacheSize
smooth: true
onStatusChanged: {
if (source == root.cachePath && status === Image.Error) {
source = root.encodedImagePath;
return;
}
if (root.isRemoteUrl || source != root.encodedImagePath || status !== Image.Ready || !root.cachePath)
return;
Paths.mkdir(Paths.imagecache);
const grabPath = root.cachePath;
if (visible && width > 0 && height > 0 && Window.window?.visible) {
grabToImage(res => res.saveToFile(grabPath));
}
}
}
onImagePathChanged: {
if (!imagePath) {
source = "";
staticImg.source = "";
return;
}
if (isAnimated)
return;
if (isRemoteUrl) {
source = imagePath;
staticImg.source = imagePath;
return;
}
Paths.mkdir(Paths.imagecache);
const hash = djb2Hash(normalizedPath);
const cPath = hash ? `${Paths.stringify(Paths.imagecache)}/${hash}@${maxCacheSize}x${maxCacheSize}.png` : "";
const encoded = "file://" + normalizedPath.split('/').map(s => encodeURIComponent(s)).join('/');
source = cPath || encoded;
}
onStatusChanged: {
if (source == cachePath && status === Image.Error) {
source = encodedImagePath;
return;
}
if (isRemoteUrl || source != encodedImagePath || status !== Image.Ready || !cachePath)
return;
Paths.mkdir(Paths.imagecache);
const grabPath = cachePath;
if (visible && width > 0 && height > 0 && Window.window?.visible) {
grabToImage(res => res.saveToFile(grabPath));
}
staticImg.source = cPath || encoded;
}
}

View File

@@ -396,7 +396,6 @@ Item {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.currentValue = delegateRoot.modelData;
root.valueChanged(delegateRoot.modelData);
dropdownMenu.close();
}

View File

@@ -5,6 +5,7 @@ import qs.Widgets
Flickable {
id: flickable
property alias verticalScrollBar: vbar
property real mouseWheelSpeed: 60
property real momentumVelocity: 0
property bool isMomentumActive: false

View File

@@ -5,12 +5,15 @@ import qs.Common
ScrollBar {
id: scrollbar
property var targetFlickable: null
readonly property var _target: targetFlickable ?? parent
property bool _scrollBarActive: false
property alias hideTimer: hideScrollBarTimer
property bool _isParentMoving: parent && (parent.moving || parent.flicking || parent.isMomentumActive)
property bool _isParentMoving: _target && (_target.moving || _target.flicking || _target.isMomentumActive)
property bool _shouldShow: pressed || hovered || active || _isParentMoving || _scrollBarActive
policy: (parent && parent.contentHeight > parent.height) ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff
policy: (_target && _target.contentHeight > _target.height) ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff
minimumSize: 0.08
implicitWidth: 10
interactive: true

View File

@@ -17,13 +17,13 @@
/* customize things here */
:root {
/* font, change to 'gg sans' for default discord font*/
--font: 'figtree';
--font: 'gg sans';
/* top left corner text */
--corner-text: 'Midnight';
/* color of status indicators and window controls */
--online-indicator: {{colors.inverse_primary.default.hex}}; /* change to #23a55a for default green */
--online-indicator: {{colors.inverse_primary.default.hex}}; /* change to #23a55a for default green */
--dnd-indicator: {{colors.error.default.hex}}; /* change to #f13f43 for default red */
--idle-indicator: {{colors.tertiary_container.default.hex}}; /* change to #f0b232 for default yellow */
--streaming-indicator: {{colors.on_primary.default.hex}}; /* change to #593695 for default purple */
@@ -34,11 +34,11 @@
--accent-3: {{colors.primary.default.hex}}; /* accent buttons */
--accent-4: {{colors.surface_bright.default.hex}}; /* accent buttons when hovered */
--accent-5: {{colors.primary_fixed_dim.default.hex}}; /* accent buttons when clicked */
--mention: {{colors.background.default.hex}}; /* mentions & mention messages */
--mention: {{colors.surface.default.hex}}; /* mentions & mention messages */
--mention-hover: {{colors.surface_bright.default.hex}}; /* mentions & mention messages when hovered */
/* text colors */
--text-0: {{colors.background.default.hex}}; /* text on colored elements */
--text-0: {{colors.surface.default.hex}}; /* text on colored elements */
--text-1: {{colors.on_surface.default.hex}}; /* other normally white text */
--text-2: {{colors.on_surface.default.hex}}; /* headings and important text */
--text-3: {{colors.on_surface_variant.default.hex}}; /* normal text */
@@ -46,10 +46,10 @@
--text-5: {{colors.outline.default.hex}}; /* muted channels/chats and timestamps */
/* background and dark colors */
--bg-1: {{colors.primary.default.hex}}; /* dark buttons when clicked */
--bg-2: {{colors.surface_container.default.hex}}; /* dark buttons */
--bg-1: {{colors.surface_variant.default.hex}}; /* dark buttons when clicked */
--bg-2: {{colors.surface_container_high.default.hex}}; /* dark buttons */
--bg-3: {{colors.surface_container_low.default.hex}}; /* spacing, secondary elements */
--bg-4: {{colors.background.default.hex}}; /* main background color */
--bg-4: {{colors.surface.default.hex}}; /* main background color */
--hover: {{colors.surface_bright.default.hex}}; /* channels and buttons when hovered */
--active: {{colors.surface_bright.default.hex}}; /* channels and buttons when clicked or selected */
--message-hover: {{colors.surface_bright.default.hex}}; /* messages when hovered */

File diff suppressed because it is too large Load Diff

View File

@@ -281,6 +281,9 @@
"Anonymous Identity (optional)": {
"Anonymous Identity (optional)": "Identidad anónima (opcional)"
},
"App Customizations": {
"App Customizations": ""
},
"App ID Substitutions": {
"App ID Substitutions": ""
},
@@ -305,12 +308,21 @@
"Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates.": {
"Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates.": "Aplicar un tono cálido para disminuir la fatiga visual. Configure abajo la automatización para definir cuándo se activa."
},
"Apps": {
"Apps": ""
},
"Apps Dock": {
"Apps Dock": ""
},
"Apps Icon": {
"Apps Icon": "Apps"
},
"Apps are ordered by usage frequency, then last used, then alphabetically.": {
"Apps are ordered by usage frequency, then last used, then alphabetically.": "Las aplicaciones son ordenadas por frecuencia de uso, luego por último utilizado, y luego alfabéticamente."
},
"Apps with custom display name, icon, or launch options. Right-click an app and select 'Edit App' to customize.": {
"Apps with custom display name, icon, or launch options. Right-click an app and select 'Edit App' to customize.": ""
},
"Arrange displays and configure resolution, refresh rate, and VRR": {
"Arrange displays and configure resolution, refresh rate, and VRR": "Organice las pantallas y configure la resolución, la frecuencia de actualización y el VRR."
},
@@ -410,6 +422,9 @@
"Autoconnect enabled": {
"Autoconnect enabled": "Conexión automática activada"
},
"Automatic Color Mode": {
"Automatic Color Mode": ""
},
"Automatic Control": {
"Automatic Control": "Control Automático"
},
@@ -434,6 +449,9 @@
"Automatically lock the screen when the system prepares to suspend": {
"Automatically lock the screen when the system prepares to suspend": "Bloquear pantalla cuando el sistema se vaya a suspender"
},
"Automation": {
"Automation": ""
},
"Available": {
"Available": "Disponible"
},
@@ -578,6 +596,9 @@
"Browse Plugins": {
"Browse Plugins": "Explorar Complementos"
},
"Browse or search plugins": {
"Browse or search plugins": ""
},
"CPU": {
"CPU": "CPU"
},
@@ -608,6 +629,12 @@
"CUPS not available": {
"CUPS not available": "CUPS no esta disponibl"
},
"Calc": {
"Calc": ""
},
"Calculator": {
"Calculator": ""
},
"Camera": {
"Camera": "Cámara"
},
@@ -659,6 +686,9 @@
"Choose Color": {
"Choose Color": "Elegir color"
},
"Choose Dock Launcher Logo Color": {
"Choose Dock Launcher Logo Color": ""
},
"Choose Launcher Logo Color": {
"Choose Launcher Logo Color": "Elegir Color del Icono en el Lanzador"
},
@@ -695,6 +725,9 @@
"Choose which monitor shows the lock screen interface. Other monitors will display a solid color for OLED burn-in protection.": {
"Choose which monitor shows the lock screen interface. Other monitors will display a solid color for OLED burn-in protection.": "Escoger en cuál monitor se muestra la interfaz de la pantalla de bloqueo. Otros mostrarán un color sólido para proteger las pantallas OLED contra quemaduras."
},
"Chroma Style": {
"Chroma Style": ""
},
"Cipher": {
"Cipher": "Cifrado"
},
@@ -770,9 +803,15 @@
"Close": {
"Close": "Cerrar"
},
"Close All Windows": {
"Close All Windows": ""
},
"Close Overview on Launch": {
"Close Overview on Launch": "Cerrar vista general al iniciar"
},
"Close Window": {
"Close Window": ""
},
"Color": {
"Color": "Color"
},
@@ -803,6 +842,12 @@
"Color temperature for night mode": {
"Color temperature for night mode": "Temperatura de color para el modo nocturno"
},
"Color theme for syntax highlighting.": {
"Color theme for syntax highlighting.": ""
},
"Color theme for syntax highlighting. %1 themes available.": {
"Color theme for syntax highlighting. %1 themes available.": ""
},
"Colorful mix of bright contrasting accents.": {
"Colorful mix of bright contrasting accents.": "Colores vivos con contrastes luminosos."
},
@@ -812,6 +857,9 @@
"Command": {
"Command": "Comando"
},
"Commands": {
"Commands": ""
},
"Communication": {
"Communication": "Comunicación"
},
@@ -905,6 +953,9 @@
"Control currently playing media": {
"Control currently playing media": "Controlar la reproducción en curso"
},
"Control which plugins appear in 'All' mode without requiring a trigger prefix. Drag to reorder.": {
"Control which plugins appear in 'All' mode without requiring a trigger prefix. Drag to reorder.": ""
},
"Control workspaces and columns by scrolling on the bar": {
"Control workspaces and columns by scrolling on the bar": "Controle los espacios de trabajo y las columnas desplazándose por la barra"
},
@@ -926,6 +977,9 @@
"Copy Full Command": {
"Copy Full Command": ""
},
"Copy HTML": {
"Copy HTML": ""
},
"Copy Name": {
"Copy Name": ""
},
@@ -935,6 +989,15 @@
"Copy Process Name": {
"Copy Process Name": "Copiar Nombre del Proceso"
},
"Copy Text": {
"Copy Text": ""
},
"Copy URL": {
"Copy URL": ""
},
"Copy path": {
"Copy path": ""
},
"Corner Radius": {
"Corner Radius": "Radio de la esquina"
},
@@ -1343,6 +1406,9 @@
"Edge Spacing": {
"Edge Spacing": "Espacio entre bordes"
},
"Edit App": {
"Edit App": ""
},
"Education": {
"Education": "Educación"
},
@@ -1391,6 +1457,9 @@
"Enable loginctl lock integration": {
"Enable loginctl lock integration": "Habilitar integración con loginctl"
},
"Enable media player controls on the lock screen window": {
"Show Media Player": ""
},
"Enable password field display on the lock screen window": {
"Show Password Field": "Mostrar campo de contraseña"
},
@@ -1427,6 +1496,9 @@
"Enter PIN for ": {
"Enter PIN for ": "Ingresar PIN para "
},
"Enter a new name for this workspace": {
"Enter a new name for this workspace": ""
},
"Enter a search query": {
"Enter a search query": "Ingresar texto a buscar"
},
@@ -1466,6 +1538,9 @@
"Entry unpinned": {
"Entry unpinned": ""
},
"Environment Variables": {
"Environment Variables": ""
},
"Error": {
"Error": "Error"
},
@@ -1481,6 +1556,9 @@
"Exponential": {
"Exponential": "Exponencial"
},
"Extra Arguments": {
"Extra Arguments": ""
},
"F1/I: Toggle • F10: Help": {
"F1/I: Toggle • F10: Help": "F1/I: Accionar • F10: Ayudar"
},
@@ -1670,6 +1748,9 @@
"File Information": {
"File Information": "Información del archivo"
},
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": ""
},
"Files": {
"Files": "Archivos"
},
@@ -1868,6 +1949,9 @@
"HSV": {
"HSV": "HSV"
},
"HTML copied to clipboard": {
"HTML copied to clipboard": ""
},
"Health": {
"Health": "Salud"
},
@@ -1886,9 +1970,18 @@
"Hidden": {
"Hidden": ""
},
"Hidden Apps": {
"Hidden Apps": ""
},
"Hidden Network": {
"Hidden Network": ""
},
"Hidden apps won't appear in the launcher. Right-click an app and select 'Hide App' to hide it.": {
"Hidden apps won't appear in the launcher. Right-click an app and select 'Hide App' to hide it.": ""
},
"Hide App": {
"Hide App": ""
},
"Hide Delay": {
"Hide Delay": "Ocultar Retardo"
},
@@ -1970,6 +2063,9 @@
"IP Address:": {
"IP Address:": "Dirección IP:"
},
"Icon": {
"Icon": ""
},
"Icon Size": {
"Icon Size": "Tamaño de icono"
},
@@ -2009,6 +2105,9 @@
"Include Transitions": {
"Include Transitions": "Incluir transiciones"
},
"Include desktop actions (shortcuts) in search results.": {
"Include desktop actions (shortcuts) in search results.": ""
},
"Incompatible Plugins Loaded": {
"Incompatible Plugins Loaded": "Plugins incompatibles cargados"
},
@@ -2114,11 +2213,18 @@
"Failed to reject pairing": "",
"Failed to ring device": "",
"Failed to send clipboard": "",
"Failed to send file": "",
"Failed to send ping": "",
"Failed to share": "",
"Pairing failed": "",
"Unpair failed": ""
},
"KDE Connect file browser title": {
"Select File to Send": ""
},
"KDE Connect file send": {
"Sending": ""
},
"KDE Connect file share notification": {
"File received from": ""
},
@@ -2184,15 +2290,24 @@
"KDE Connect ring tooltip": {
"Ring": ""
},
"KDE Connect send file button": {
"Send File": ""
},
"KDE Connect service unavailable message": {
"KDE Connect unavailable": ""
},
"KDE Connect share URL button": {
"Share URL": ""
},
"KDE Connect share button": {
"Share Text": ""
},
"KDE Connect share button | KDE Connect share dialog title | KDE Connect share tooltip": {
"Share": ""
},
"KDE Connect share dialog title | KDE Connect share tooltip": {
"Share": ""
},
"KDE Connect share input placeholder": {
"Enter URL or text to share": ""
},
@@ -2265,6 +2380,12 @@
"LED device": {
"LED device": "Dispositivo LED"
},
"Large": {
"Large": ""
},
"Largest": {
"Largest": ""
},
"Last launched %1": {
"Last launched %1": "Usado hace %1"
},
@@ -2295,6 +2416,9 @@
"Launcher": {
"Launcher": "Lanzador"
},
"Launcher Button": {
"Launcher Button": ""
},
"Launcher Button Logo": {
"Launcher Button Logo": "Icono del lanzador"
},
@@ -2334,6 +2458,9 @@
"Loading plugins...": {
"Loading plugins...": "Cargando complementos..."
},
"Loading trending...": {
"Loading trending...": ""
},
"Loading...": {
"Loading...": "Cargando..."
},
@@ -2709,6 +2836,12 @@
"No adapters": {
"No adapters": "Sin adaptadores"
},
"No app customizations.": {
"No app customizations.": ""
},
"No apps found": {
"No apps found": ""
},
"No battery": {
"No battery": "Sin bateria"
},
@@ -2739,15 +2872,24 @@
"No files found": {
"No files found": "Archivos no encontrados"
},
"No hidden apps.": {
"No hidden apps.": ""
},
"No items added yet": {
"No items added yet": "No hay nada añadido todavía"
},
"No keybinds found": {
"No keybinds found": "No se encontraron atajos"
},
"No launcher plugins installed.": {
"No launcher plugins installed.": ""
},
"No matches": {
"No matches": "Sin coincidencias"
},
"No plugin results": {
"No plugin results": ""
},
"No plugins found": {
"No plugins found": "No hay complementos encontrados"
},
@@ -2766,9 +2908,15 @@
"No recent clipboard entries found": {
"No recent clipboard entries found": ""
},
"No results found": {
"No results found": ""
},
"No saved clipboard entries": {
"No saved clipboard entries": ""
},
"No trigger": {
"No trigger": ""
},
"No variants created. Click Add to create a new monitor widget.": {
"No variants created. Click Add to create a new monitor widget.": "No hay variantes creadas. Haga clic en Añadir para crear un nuevo widget de monitor."
},
@@ -2880,6 +3028,12 @@
"Open Notepad File": {
"Open Notepad File": "Abrir archivo de Notas"
},
"Open folder": {
"Open folder": ""
},
"Open in Browser": {
"Open in Browser": ""
},
"Open search bar to find text": {
"Open search bar to find text": "Abrir barra de búsqueda para encontrar texto"
},
@@ -2946,6 +3100,9 @@
"PIN": {
"PIN": "PIN"
},
"Pad Hours": {
"Pad Hours": ""
},
"Padding": {
"Padding": "Relleno"
},
@@ -3012,6 +3169,9 @@
"Pinned": {
"Pinned": "Fijado"
},
"Pinned and running apps with drag-and-drop": {
"Pinned and running apps with drag-and-drop": ""
},
"Place plugin directories here. Each plugin should have a plugin.json manifest file.": {
"Place plugin directories here. Each plugin should have a plugin.json manifest file.": "Pon aquí el directorio de complementos. Cada complemento debe tener un archivo plugin.json adjunto."
},
@@ -3048,6 +3208,9 @@
"Plugin Management": {
"Plugin Management": "Gestión de complementos"
},
"Plugin Visibility": {
"Plugin Visibility": ""
},
"Plugin is disabled - enable in Plugins settings to use": {
"Plugin is disabled - enable in Plugins settings to use": "Complemento desactivado actívalo en los ajustes de complementos"
},
@@ -3126,6 +3289,9 @@
"Prevent screen timeout": {
"Prevent screen timeout": "Evitar que la pantalla se apague por inactividad"
},
"Preview": {
"Preview": ""
},
"Primary": {
"Primary": "Primario"
},
@@ -3240,6 +3406,12 @@
"Remove gaps and border when windows are maximized": {
"Remove gaps and border when windows are maximized": "Eliminar espacios y bordes cuando las ventanas estan maximizadas"
},
"Rename": {
"Rename": ""
},
"Rename Workspace": {
"Rename Workspace": ""
},
"Repeat": {
"Repeat": ""
},
@@ -3417,6 +3589,12 @@
"Scrolling": {
"Scrolling": "Desplazamiento"
},
"Search App Actions": {
"Search App Actions": ""
},
"Search Options": {
"Search Options": ""
},
"Search by key combo, description, or action name.\\n\\nDefault action copies the keybind to clipboard.\\nRight-click or press Right Arrow to pin frequently used keybinds - they'll appear at the top when not searching.": {
"Search by key combo, description, or action name.\\n\\nDefault action copies the keybind to clipboard.\\nRight-click or press Right Arrow to pin frequently used keybinds - they'll appear at the top when not searching.": ""
},
@@ -3456,6 +3634,9 @@
"Security": {
"Security": "Seguridad"
},
"Select": {
"Select": ""
},
"Select Application": {
"Select Application": "Seleccionar una aplicación"
},
@@ -3534,12 +3715,18 @@
"Setup": {
"Setup": "Configurar"
},
"Share Gamma Control Settings": {
"Share Gamma Control Settings": ""
},
"Shell": {
"Shell": "Shell"
},
"Shift+Del: Clear All • Esc: Close": {
"Shift+Del: Clear All • Esc: Close": "Shift+Del: Borrar todo • Esc: Cerrar "
},
"Shift+Enter to paste": {
"Shift+Enter to paste": ""
},
"Shift+Enter: Paste • Shift+Del: Clear All • Esc: Close": {
"Shift+Enter: Paste • Shift+Del: Clear All • Esc: Close": "Mayús+Intro: Pegar - Mayús+Supr: Borrar todo - Esc: Cerrar"
},
@@ -3597,6 +3784,9 @@
"Show Humidity": {
"Show Humidity": ""
},
"Show Launcher Button": {
"Show Launcher Button": ""
},
"Show Line Numbers": {
"Show Line Numbers": "Mostrar números de líneas"
},
@@ -3774,6 +3964,9 @@
"Sizing": {
"Sizing": "Redimensionamiento"
},
"Small": {
"Small": ""
},
"Smartcard Authentication": {
"Smartcard Authentication": "Autenticación con tarjeta inteligente"
},
@@ -4095,12 +4288,24 @@
"Transparency": {
"Transparency": "Transparencia"
},
"Trending GIFs": {
"Trending GIFs": ""
},
"Trending Stickers": {
"Trending Stickers": ""
},
"Trigger": {
"Trigger": ""
},
"Trigger Prefix": {
"Trigger Prefix": ""
},
"Trigger: %1": {
"Trigger: %1": ""
},
"Try a different search": {
"Try a different search": ""
},
"Turn off all displays immediately when the lock screen activates": {
"Turn off all displays immediately when the lock screen activates": ""
},
@@ -4110,9 +4315,21 @@
"Type": {
"Type": "Tipo"
},
"Type at least 2 characters": {
"Type at least 2 characters": ""
},
"Type this prefix to search keybinds": {
"Type this prefix to search keybinds": ""
},
"Type to search": {
"Type to search": ""
},
"Type to search apps": {
"Type to search apps": ""
},
"Type to search files": {
"Type to search files": ""
},
"Typography": {
"Typography": "Tipografía"
},
@@ -4230,6 +4447,9 @@
"Use light theme instead of dark theme": {
"Use light theme instead of dark theme": "Usar tema claro en lugar del tema oscuro"
},
"Use meters per second instead of km/h for wind speed": {
"Use meters per second instead of km/h for wind speed": ""
},
"Use smaller notification cards": {
"Use smaller notification cards": ""
},
@@ -4257,9 +4477,15 @@
"Username": {
"Username": "Nombre de usuario"
},
"Uses sunrise/sunset times based on your location.": {
"Uses sunrise/sunset times based on your location.": ""
},
"Uses sunrise/sunset times to automatically adjust night mode based on your location.": {
"Uses sunrise/sunset times to automatically adjust night mode based on your location.": "Usar las horas de amanecer/anochecer para ajustar automáticamente el modo noche basándose en tu localización."
},
"Using shared settings from Gamma Control": {
"Using shared settings from Gamma Control": ""
},
"Utilities": {
"Utilities": "Utilidades"
},
@@ -4460,6 +4686,9 @@
"Wind Speed": {
"Wind Speed": "Velocidad del viento"
},
"Wind Speed in m/s": {
"Wind Speed in m/s": ""
},
"Window Corner Radius": {
"Window Corner Radius": "Radio de la esquina de la ventana"
},
@@ -4493,6 +4722,9 @@
"Workspace Switcher": {
"Workspace Switcher": "Espacios de trabajo"
},
"Workspace name": {
"Workspace name": ""
},
"Workspaces": {
"Workspaces": "Espacios de trabajo"
},
@@ -4520,6 +4752,9 @@
"You have unsaved changes. Save before opening a file?": {
"You have unsaved changes. Save before opening a file?": "Tienes cambios sin guardar. ¿Guardar antes de abrir otro archivo?"
},
"actions": {
"actions": ""
},
"apps": {
"apps": "apps"
},
@@ -4529,6 +4764,12 @@
"bar shadow settings card": {
"Shadow": ""
},
"border color": {
"Color": ""
},
"border thickness": {
"Thickness": ""
},
"browse themes button | theme browser header | theme browser window title": {
"Browse Themes": "Busqueda de temas"
},
@@ -4786,6 +5027,21 @@
"installed status": {
"Installed": "Instalado"
},
"launcher appearance settings": {
"Appearance": ""
},
"launcher border option": {
"Border": ""
},
"launcher footer description": {
"Show mode tabs and keyboard hints at the bottom.": ""
},
"launcher footer visibility": {
"Show Footer": ""
},
"launcher size option": {
"Size": ""
},
"leave empty for default": {
"leave empty for default": "deja vacio para valor por defecto"
},
@@ -4826,6 +5082,9 @@
"ms": {
"ms": "ms"
},
"nav": {
"nav": ""
},
"no custom theme file status": {
"No custom theme file": "Ningún archivo de tema personalizado"
},
@@ -4882,6 +5141,12 @@
"official": {
"official": "oficial"
},
"open": {
"open": ""
},
"outline color": {
"Outline": ""
},
"plugin browser description": {
"Install plugins from the DMS plugin registry": "Instalar plugins desde el registro de plugins de DMS"
},
@@ -4897,6 +5162,9 @@
"plugin search placeholder": {
"Search plugins...": "Buscar complementos..."
},
"primary color": {
"Primary": ""
},
"process count label in footer": {
"Processes:": ""
},
@@ -4915,6 +5183,9 @@
"registry theme description": {
"Color theme from DMS registry": "Tema de color del registro DMS"
},
"secondary color": {
"Secondary": ""
},
"seconds": {
"seconds": "segundos"
},
@@ -4928,6 +5199,9 @@
"Surface": "",
"Text": ""
},
"shadow color option | text color": {
"Text": ""
},
"shadow intensity slider": {
"Intensity": ""
},
@@ -5007,6 +5281,9 @@
"wallpaper settings external management": {
"External Wallpaper Management": "Manejo externo del fondo de pantalla"
},
"weather feels like temperature": {
"Feels Like %1°": ""
},
"wtype not available - install wtype for paste support": {
"wtype not available - install wtype for paste support": "wtype no disponible - instala wtype para soporte de pegado"
},

View File

@@ -281,6 +281,9 @@
"Anonymous Identity (optional)": {
"Anonymous Identity (optional)": "هویت ناشناس (اختیاری)"
},
"App Customizations": {
"App Customizations": ""
},
"App ID Substitutions": {
"App ID Substitutions": ""
},
@@ -305,12 +308,21 @@
"Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates.": {
"Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates.": "برای کاهش خستگی چشم دمای رنگ گرم را اعمال کن. از تنظیمات خودکارسازی پایین برای زمان فعال شدن آن استفاده کنید."
},
"Apps": {
"Apps": ""
},
"Apps Dock": {
"Apps Dock": ""
},
"Apps Icon": {
"Apps Icon": "آیکون برنامه‌ها"
},
"Apps are ordered by usage frequency, then last used, then alphabetically.": {
"Apps are ordered by usage frequency, then last used, then alphabetically.": "برنامه‌ها بر اساس دفعات استفاده، سپس آخرین استفاده و در نهایت بر اساس حروف الفبا مرتب شده‌اند."
},
"Apps with custom display name, icon, or launch options. Right-click an app and select 'Edit App' to customize.": {
"Apps with custom display name, icon, or launch options. Right-click an app and select 'Edit App' to customize.": ""
},
"Arrange displays and configure resolution, refresh rate, and VRR": {
"Arrange displays and configure resolution, refresh rate, and VRR": "چیدمان نمایشگرها و پیکربندی وضوح، نرخ تازه‌سازی و VRR"
},
@@ -410,6 +422,9 @@
"Autoconnect enabled": {
"Autoconnect enabled": "اتصال خودکار فعال"
},
"Automatic Color Mode": {
"Automatic Color Mode": ""
},
"Automatic Control": {
"Automatic Control": "کنترل خودکار"
},
@@ -434,6 +449,9 @@
"Automatically lock the screen when the system prepares to suspend": {
"Automatically lock the screen when the system prepares to suspend": "قفل خودکار صفحه هنگام آماده‌شدن سیستم برای تعلیق"
},
"Automation": {
"Automation": ""
},
"Available": {
"Available": "در دسترس"
},
@@ -578,6 +596,9 @@
"Browse Plugins": {
"Browse Plugins": "مرور افزونه‌ها"
},
"Browse or search plugins": {
"Browse or search plugins": ""
},
"CPU": {
"CPU": "CPU"
},
@@ -608,6 +629,12 @@
"CUPS not available": {
"CUPS not available": "CUPS در دسترس نیست"
},
"Calc": {
"Calc": ""
},
"Calculator": {
"Calculator": ""
},
"Camera": {
"Camera": "دوربین"
},
@@ -659,6 +686,9 @@
"Choose Color": {
"Choose Color": "انتخاب رنگ"
},
"Choose Dock Launcher Logo Color": {
"Choose Dock Launcher Logo Color": ""
},
"Choose Launcher Logo Color": {
"Choose Launcher Logo Color": "انتخاب رنگ لوگوی لانچر"
},
@@ -695,6 +725,9 @@
"Choose which monitor shows the lock screen interface. Other monitors will display a solid color for OLED burn-in protection.": {
"Choose which monitor shows the lock screen interface. Other monitors will display a solid color for OLED burn-in protection.": "انتخاب کنید که کدام مانیتور رابط صفحه قفل را نشان دهد. مانیتورهای دیگر برای محافظت در برابر سوختگی OLED، رنگ ثابتی را نمایش می‌دهند."
},
"Chroma Style": {
"Chroma Style": ""
},
"Cipher": {
"Cipher": "رمزگذار"
},
@@ -770,9 +803,15 @@
"Close": {
"Close": "بستن"
},
"Close All Windows": {
"Close All Windows": ""
},
"Close Overview on Launch": {
"Close Overview on Launch": "بستن نمای کلی هنگام اجرا"
},
"Close Window": {
"Close Window": ""
},
"Color": {
"Color": "رنگ"
},
@@ -803,6 +842,12 @@
"Color temperature for night mode": {
"Color temperature for night mode": "دمای رنگ برای حالت شب"
},
"Color theme for syntax highlighting.": {
"Color theme for syntax highlighting.": ""
},
"Color theme for syntax highlighting. %1 themes available.": {
"Color theme for syntax highlighting. %1 themes available.": ""
},
"Colorful mix of bright contrasting accents.": {
"Colorful mix of bright contrasting accents.": "ترکیب رنگارنگ از رنگ‌های تأکیدی متضاد."
},
@@ -812,6 +857,9 @@
"Command": {
"Command": "دستور"
},
"Commands": {
"Commands": ""
},
"Communication": {
"Communication": "ارتباطات"
},
@@ -905,6 +953,9 @@
"Control currently playing media": {
"Control currently playing media": "کنترل رسانه درحال پخش"
},
"Control which plugins appear in 'All' mode without requiring a trigger prefix. Drag to reorder.": {
"Control which plugins appear in 'All' mode without requiring a trigger prefix. Drag to reorder.": ""
},
"Control workspaces and columns by scrolling on the bar": {
"Control workspaces and columns by scrolling on the bar": "کنترل workspaceها و ستون‌ها با اسکرول روی نوار"
},
@@ -926,6 +977,9 @@
"Copy Full Command": {
"Copy Full Command": ""
},
"Copy HTML": {
"Copy HTML": ""
},
"Copy Name": {
"Copy Name": ""
},
@@ -935,6 +989,15 @@
"Copy Process Name": {
"Copy Process Name": "کپی نام فرایند"
},
"Copy Text": {
"Copy Text": ""
},
"Copy URL": {
"Copy URL": ""
},
"Copy path": {
"Copy path": ""
},
"Corner Radius": {
"Corner Radius": "شعاع گوشه"
},
@@ -1343,6 +1406,9 @@
"Edge Spacing": {
"Edge Spacing": "فاصله لبه"
},
"Edit App": {
"Edit App": ""
},
"Education": {
"Education": "آموزش"
},
@@ -1391,6 +1457,9 @@
"Enable loginctl lock integration": {
"Enable loginctl lock integration": "فعال‌کردن یکپارچه‌سازی قفل loginctl"
},
"Enable media player controls on the lock screen window": {
"Show Media Player": ""
},
"Enable password field display on the lock screen window": {
"Show Password Field": "نمایش فیلد گذرواژه"
},
@@ -1427,6 +1496,9 @@
"Enter PIN for ": {
"Enter PIN for ": "ورود PIN برای "
},
"Enter a new name for this workspace": {
"Enter a new name for this workspace": ""
},
"Enter a search query": {
"Enter a search query": "یک عبارت جستجو وارد کنید"
},
@@ -1466,6 +1538,9 @@
"Entry unpinned": {
"Entry unpinned": ""
},
"Environment Variables": {
"Environment Variables": ""
},
"Error": {
"Error": "خطا"
},
@@ -1481,6 +1556,9 @@
"Exponential": {
"Exponential": "نمایی"
},
"Extra Arguments": {
"Extra Arguments": ""
},
"F1/I: Toggle • F10: Help": {
"F1/I: Toggle • F10: Help": "F1/I: تغییر حالت • F10: راهنما"
},
@@ -1670,6 +1748,9 @@
"File Information": {
"File Information": "اطلاعات فایل"
},
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": ""
},
"Files": {
"Files": "فایل‌ها"
},
@@ -1868,6 +1949,9 @@
"HSV": {
"HSV": "HSV"
},
"HTML copied to clipboard": {
"HTML copied to clipboard": ""
},
"Health": {
"Health": "سلامت"
},
@@ -1886,9 +1970,18 @@
"Hidden": {
"Hidden": ""
},
"Hidden Apps": {
"Hidden Apps": ""
},
"Hidden Network": {
"Hidden Network": ""
},
"Hidden apps won't appear in the launcher. Right-click an app and select 'Hide App' to hide it.": {
"Hidden apps won't appear in the launcher. Right-click an app and select 'Hide App' to hide it.": ""
},
"Hide App": {
"Hide App": ""
},
"Hide Delay": {
"Hide Delay": "تأخیر پنهان‌شدن"
},
@@ -1970,6 +2063,9 @@
"IP Address:": {
"IP Address:": "آدرس IP:"
},
"Icon": {
"Icon": ""
},
"Icon Size": {
"Icon Size": "اندازه آیکون"
},
@@ -2009,6 +2105,9 @@
"Include Transitions": {
"Include Transitions": "اضافه‌کردن گذارها"
},
"Include desktop actions (shortcuts) in search results.": {
"Include desktop actions (shortcuts) in search results.": ""
},
"Incompatible Plugins Loaded": {
"Incompatible Plugins Loaded": "افزونه‌های ناسازگار بارگذاری شده‌اند"
},
@@ -2114,11 +2213,18 @@
"Failed to reject pairing": "",
"Failed to ring device": "",
"Failed to send clipboard": "",
"Failed to send file": "",
"Failed to send ping": "",
"Failed to share": "",
"Pairing failed": "",
"Unpair failed": ""
},
"KDE Connect file browser title": {
"Select File to Send": ""
},
"KDE Connect file send": {
"Sending": ""
},
"KDE Connect file share notification": {
"File received from": ""
},
@@ -2184,15 +2290,24 @@
"KDE Connect ring tooltip": {
"Ring": ""
},
"KDE Connect send file button": {
"Send File": ""
},
"KDE Connect service unavailable message": {
"KDE Connect unavailable": ""
},
"KDE Connect share URL button": {
"Share URL": ""
},
"KDE Connect share button": {
"Share Text": ""
},
"KDE Connect share button | KDE Connect share dialog title | KDE Connect share tooltip": {
"Share": ""
},
"KDE Connect share dialog title | KDE Connect share tooltip": {
"Share": ""
},
"KDE Connect share input placeholder": {
"Enter URL or text to share": ""
},
@@ -2265,6 +2380,12 @@
"LED device": {
"LED device": "دستگاه LED"
},
"Large": {
"Large": ""
},
"Largest": {
"Largest": ""
},
"Last launched %1": {
"Last launched %1": "آخرین اجرا %1"
},
@@ -2295,6 +2416,9 @@
"Launcher": {
"Launcher": "لانچر"
},
"Launcher Button": {
"Launcher Button": ""
},
"Launcher Button Logo": {
"Launcher Button Logo": "لوگوی دکمه لانچر"
},
@@ -2334,6 +2458,9 @@
"Loading plugins...": {
"Loading plugins...": "آماده‌سازی افزونه‌ها..."
},
"Loading trending...": {
"Loading trending...": ""
},
"Loading...": {
"Loading...": "درحال بارگذاری..."
},
@@ -2709,6 +2836,12 @@
"No adapters": {
"No adapters": "آداپتوری یافت نشد"
},
"No app customizations.": {
"No app customizations.": ""
},
"No apps found": {
"No apps found": ""
},
"No battery": {
"No battery": "بدون باتری"
},
@@ -2739,15 +2872,24 @@
"No files found": {
"No files found": "هیچ فایلی یافت نشد"
},
"No hidden apps.": {
"No hidden apps.": ""
},
"No items added yet": {
"No items added yet": "هنوز آیتمی اضافه نشده"
},
"No keybinds found": {
"No keybinds found": "هیچ کلید میانبری یافت نشد"
},
"No launcher plugins installed.": {
"No launcher plugins installed.": ""
},
"No matches": {
"No matches": "موردی پیدا نشد"
},
"No plugin results": {
"No plugin results": ""
},
"No plugins found": {
"No plugins found": "افزونه‌ای یافت نشد"
},
@@ -2766,9 +2908,15 @@
"No recent clipboard entries found": {
"No recent clipboard entries found": ""
},
"No results found": {
"No results found": ""
},
"No saved clipboard entries": {
"No saved clipboard entries": ""
},
"No trigger": {
"No trigger": ""
},
"No variants created. Click Add to create a new monitor widget.": {
"No variants created. Click Add to create a new monitor widget.": "هیچ گونه‌ی دیگری ایجاد نشد. برای ایجاد یک ابزارک مانیتور جدید، روی افزودن کلیک کنید."
},
@@ -2880,6 +3028,12 @@
"Open Notepad File": {
"Open Notepad File": "باز‌کردن فایل دفترچه یادداشت"
},
"Open folder": {
"Open folder": ""
},
"Open in Browser": {
"Open in Browser": ""
},
"Open search bar to find text": {
"Open search bar to find text": "باز‌کردن نوار جستجو برای یافتن متن"
},
@@ -2946,6 +3100,9 @@
"PIN": {
"PIN": "PIN"
},
"Pad Hours": {
"Pad Hours": ""
},
"Padding": {
"Padding": "فاصله درونی"
},
@@ -3012,6 +3169,9 @@
"Pinned": {
"Pinned": "سنجاق شده"
},
"Pinned and running apps with drag-and-drop": {
"Pinned and running apps with drag-and-drop": ""
},
"Place plugin directories here. Each plugin should have a plugin.json manifest file.": {
"Place plugin directories here. Each plugin should have a plugin.json manifest file.": "دایرکتوری افزونه‌ها را اینجا قرار دهید. هر افزونه باید فایل مشخصات plugins.json داشته باشد."
},
@@ -3048,6 +3208,9 @@
"Plugin Management": {
"Plugin Management": "مدیریت افزونه"
},
"Plugin Visibility": {
"Plugin Visibility": ""
},
"Plugin is disabled - enable in Plugins settings to use": {
"Plugin is disabled - enable in Plugins settings to use": "افزونه غیرفعال است - برای استفاده آن را در تنظیمات افزونه‌ها فعال کنید"
},
@@ -3126,6 +3289,9 @@
"Prevent screen timeout": {
"Prevent screen timeout": "جلوگیری از خاموش‌شدن صفحه"
},
"Preview": {
"Preview": ""
},
"Primary": {
"Primary": "اصلی"
},
@@ -3240,6 +3406,12 @@
"Remove gaps and border when windows are maximized": {
"Remove gaps and border when windows are maximized": "حذف فاصله‌ها و حاشیه هنگام بزرگ‌کردن پنجره‌ها"
},
"Rename": {
"Rename": ""
},
"Rename Workspace": {
"Rename Workspace": ""
},
"Repeat": {
"Repeat": ""
},
@@ -3417,6 +3589,12 @@
"Scrolling": {
"Scrolling": "اسکرولینگ"
},
"Search App Actions": {
"Search App Actions": ""
},
"Search Options": {
"Search Options": ""
},
"Search by key combo, description, or action name.\\n\\nDefault action copies the keybind to clipboard.\\nRight-click or press Right Arrow to pin frequently used keybinds - they'll appear at the top when not searching.": {
"Search by key combo, description, or action name.\\n\\nDefault action copies the keybind to clipboard.\\nRight-click or press Right Arrow to pin frequently used keybinds - they'll appear at the top when not searching.": ""
},
@@ -3456,6 +3634,9 @@
"Security": {
"Security": "امنیت"
},
"Select": {
"Select": ""
},
"Select Application": {
"Select Application": "انتخاب برنامه"
},
@@ -3534,12 +3715,18 @@
"Setup": {
"Setup": "راه‌اندازی"
},
"Share Gamma Control Settings": {
"Share Gamma Control Settings": ""
},
"Shell": {
"Shell": "شِل"
},
"Shift+Del: Clear All • Esc: Close": {
"Shift+Del: Clear All • Esc: Close": "Shift+Del: پاک‌کردن همه • Esc: بستن"
},
"Shift+Enter to paste": {
"Shift+Enter to paste": ""
},
"Shift+Enter: Paste • Shift+Del: Clear All • Esc: Close": {
"Shift+Enter: Paste • Shift+Del: Clear All • Esc: Close": "Shift+Enter: الصاق • Shift+Del: پاک‌کردن همه • Esc: بستن"
},
@@ -3597,6 +3784,9 @@
"Show Humidity": {
"Show Humidity": "نمایش رطوبت"
},
"Show Launcher Button": {
"Show Launcher Button": ""
},
"Show Line Numbers": {
"Show Line Numbers": "نمایش شماره خطوط"
},
@@ -3774,6 +3964,9 @@
"Sizing": {
"Sizing": "اندازه‌دهی"
},
"Small": {
"Small": ""
},
"Smartcard Authentication": {
"Smartcard Authentication": "احراز هویت با کارت هوشمند"
},
@@ -4095,12 +4288,24 @@
"Transparency": {
"Transparency": "شفافیت"
},
"Trending GIFs": {
"Trending GIFs": ""
},
"Trending Stickers": {
"Trending Stickers": ""
},
"Trigger": {
"Trigger": ""
},
"Trigger Prefix": {
"Trigger Prefix": ""
},
"Trigger: %1": {
"Trigger: %1": ""
},
"Try a different search": {
"Try a different search": ""
},
"Turn off all displays immediately when the lock screen activates": {
"Turn off all displays immediately when the lock screen activates": ""
},
@@ -4110,9 +4315,21 @@
"Type": {
"Type": "نوع"
},
"Type at least 2 characters": {
"Type at least 2 characters": ""
},
"Type this prefix to search keybinds": {
"Type this prefix to search keybinds": ""
},
"Type to search": {
"Type to search": ""
},
"Type to search apps": {
"Type to search apps": ""
},
"Type to search files": {
"Type to search files": ""
},
"Typography": {
"Typography": "تایپوگرافی"
},
@@ -4230,6 +4447,9 @@
"Use light theme instead of dark theme": {
"Use light theme instead of dark theme": "استفاده از تم روشن به جای تم تاریک"
},
"Use meters per second instead of km/h for wind speed": {
"Use meters per second instead of km/h for wind speed": ""
},
"Use smaller notification cards": {
"Use smaller notification cards": ""
},
@@ -4257,9 +4477,15 @@
"Username": {
"Username": "نام کاربری"
},
"Uses sunrise/sunset times based on your location.": {
"Uses sunrise/sunset times based on your location.": ""
},
"Uses sunrise/sunset times to automatically adjust night mode based on your location.": {
"Uses sunrise/sunset times to automatically adjust night mode based on your location.": "از زمان طلوع/غروب خورشید برای تنظیم خودکار حالت شب بر اساس موقعیت مکانی شما استفاده می‌کند."
},
"Using shared settings from Gamma Control": {
"Using shared settings from Gamma Control": ""
},
"Utilities": {
"Utilities": "برنامه‌های کمکی"
},
@@ -4460,6 +4686,9 @@
"Wind Speed": {
"Wind Speed": "سرعت باد"
},
"Wind Speed in m/s": {
"Wind Speed in m/s": ""
},
"Window Corner Radius": {
"Window Corner Radius": "شعاع گوشه پنجره‌ها"
},
@@ -4493,6 +4722,9 @@
"Workspace Switcher": {
"Workspace Switcher": "تغییردهنده workspace"
},
"Workspace name": {
"Workspace name": ""
},
"Workspaces": {
"Workspaces": "Workspaceها"
},
@@ -4520,6 +4752,9 @@
"You have unsaved changes. Save before opening a file?": {
"You have unsaved changes. Save before opening a file?": "شما تغییرات ذخیره نشده‌ دارید. پیش از باز کردن فایل، ذخیره شوند؟"
},
"actions": {
"actions": ""
},
"apps": {
"apps": "برنامه"
},
@@ -4529,6 +4764,12 @@
"bar shadow settings card": {
"Shadow": ""
},
"border color": {
"Color": ""
},
"border thickness": {
"Thickness": ""
},
"browse themes button | theme browser header | theme browser window title": {
"Browse Themes": "مرور تم‌ها"
},
@@ -4696,7 +4937,7 @@
},
"greeter feature card title": {
"App Theming": "تم برنامه",
"Control Center": "",
"Control Center": "مرکز کنترل",
"Display Control": "کنترل نمایشگر",
"Dynamic Theming": "",
"Multi-Monitor": "چند مانیتوره",
@@ -4722,7 +4963,7 @@
"niri shortcuts config": "پیکربندی میانبرهای نیری"
},
"greeter keybinds section header": {
"DMS Shortcuts": ""
"DMS Shortcuts": "میانبرهای DMS"
},
"greeter modal window title": {
"Welcome": "خوش آمدید"
@@ -4766,7 +5007,7 @@
"Features": "ویژگی‌ها"
},
"greeter welcome page tagline": {
"A modern desktop shell for Wayland compositors": ""
"A modern desktop shell for Wayland compositors": "یک پوسته دسکتاپ مدرن برای کامپازیتورهای وِیلند"
},
"greeter welcome page title": {
"Welcome to DankMaterialShell": "به DankMaterialShell خوش آمدید"
@@ -4786,6 +5027,21 @@
"installed status": {
"Installed": "نصب شده"
},
"launcher appearance settings": {
"Appearance": ""
},
"launcher border option": {
"Border": ""
},
"launcher footer description": {
"Show mode tabs and keyboard hints at the bottom.": ""
},
"launcher footer visibility": {
"Show Footer": ""
},
"launcher size option": {
"Size": ""
},
"leave empty for default": {
"leave empty for default": "برای پیش‌فرض خالی رها کنید"
},
@@ -4826,6 +5082,9 @@
"ms": {
"ms": "ms"
},
"nav": {
"nav": ""
},
"no custom theme file status": {
"No custom theme file": "هیچ تم سفارشی یافت نشد"
},
@@ -4882,6 +5141,12 @@
"official": {
"official": "رسمی"
},
"open": {
"open": ""
},
"outline color": {
"Outline": ""
},
"plugin browser description": {
"Install plugins from the DMS plugin registry": "نصب افزونه‌ها از مخزن افزونه DMS"
},
@@ -4897,6 +5162,9 @@
"plugin search placeholder": {
"Search plugins...": "جستجوی افزونه‌ها..."
},
"primary color": {
"Primary": ""
},
"process count label in footer": {
"Processes:": ""
},
@@ -4915,6 +5183,9 @@
"registry theme description": {
"Color theme from DMS registry": "رنگ تم از مخزن DMS"
},
"secondary color": {
"Secondary": ""
},
"seconds": {
"seconds": "ثانیه"
},
@@ -4928,6 +5199,9 @@
"Surface": "",
"Text": ""
},
"shadow color option | text color": {
"Text": ""
},
"shadow intensity slider": {
"Intensity": ""
},
@@ -5007,6 +5281,9 @@
"wallpaper settings external management": {
"External Wallpaper Management": "مدیریت تصویر پس‌زمینه خارجی"
},
"weather feels like temperature": {
"Feels Like %1°": ""
},
"wtype not available - install wtype for paste support": {
"wtype not available - install wtype for paste support": "wtype در دسترس نیست - wtype را برای پشتیبانی از الصاق نصب کنید"
},

File diff suppressed because it is too large Load Diff

View File

@@ -281,6 +281,9 @@
"Anonymous Identity (optional)": {
"Anonymous Identity (optional)": "זהות אנונימית (אופציונלי)"
},
"App Customizations": {
"App Customizations": ""
},
"App ID Substitutions": {
"App ID Substitutions": "החלפת ID לאפליקציות"
},
@@ -305,12 +308,21 @@
"Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates.": {
"Apply warm color temperature to reduce eye strain. Use automation settings below to control when it activates.": "הגדרת טמפרטורת צבע חמה כדי להפחית מאמץ בעיניים. השתמש/י בהגדרות האוטומציה למטה כדי לשלוט מתי ההגדרה מופעלת."
},
"Apps": {
"Apps": ""
},
"Apps Dock": {
"Apps Dock": ""
},
"Apps Icon": {
"Apps Icon": "סמל אפליקציות"
},
"Apps are ordered by usage frequency, then last used, then alphabetically.": {
"Apps are ordered by usage frequency, then last used, then alphabetically.": "האפליקציות ממוינות לפי תדירות השימוש, אחר כך לפי שימוש אחרון ולבסוף לפי סדר אלפביתי."
},
"Apps with custom display name, icon, or launch options. Right-click an app and select 'Edit App' to customize.": {
"Apps with custom display name, icon, or launch options. Right-click an app and select 'Edit App' to customize.": ""
},
"Arrange displays and configure resolution, refresh rate, and VRR": {
"Arrange displays and configure resolution, refresh rate, and VRR": "סדר/י מסכים והגדר/י רזולוציה, קצב רענון וVRR"
},
@@ -410,6 +422,9 @@
"Autoconnect enabled": {
"Autoconnect enabled": "התחברות אוטומטית מופעלת"
},
"Automatic Color Mode": {
"Automatic Color Mode": ""
},
"Automatic Control": {
"Automatic Control": "בקרה אוטומטית"
},
@@ -434,6 +449,9 @@
"Automatically lock the screen when the system prepares to suspend": {
"Automatically lock the screen when the system prepares to suspend": "נעילה אוטומטית של המסך כשהמערכת מתכוננת למצב השהיה"
},
"Automation": {
"Automation": ""
},
"Available": {
"Available": "זמין"
},
@@ -578,6 +596,9 @@
"Browse Plugins": {
"Browse Plugins": "עיון בתוספים"
},
"Browse or search plugins": {
"Browse or search plugins": ""
},
"CPU": {
"CPU": "CPU"
},
@@ -608,6 +629,12 @@
"CUPS not available": {
"CUPS not available": "CUPS אינו זמין"
},
"Calc": {
"Calc": ""
},
"Calculator": {
"Calculator": ""
},
"Camera": {
"Camera": "מצלמה"
},
@@ -659,6 +686,9 @@
"Choose Color": {
"Choose Color": "בחר/י צבע"
},
"Choose Dock Launcher Logo Color": {
"Choose Dock Launcher Logo Color": ""
},
"Choose Launcher Logo Color": {
"Choose Launcher Logo Color": "בחר/י צבע לוגו למשגר"
},
@@ -695,6 +725,9 @@
"Choose which monitor shows the lock screen interface. Other monitors will display a solid color for OLED burn-in protection.": {
"Choose which monitor shows the lock screen interface. Other monitors will display a solid color for OLED burn-in protection.": "בחר/י איזה מסך מציג את ממשק מסך הנעילה. מסכים אחרים יציגו צבע אחיד להגנה מפני צריבת OLED."
},
"Chroma Style": {
"Chroma Style": ""
},
"Cipher": {
"Cipher": "צופן"
},
@@ -770,9 +803,15 @@
"Close": {
"Close": "סגירה"
},
"Close All Windows": {
"Close All Windows": ""
},
"Close Overview on Launch": {
"Close Overview on Launch": "סגור/י את הסקירה בעת הפעלה"
},
"Close Window": {
"Close Window": ""
},
"Color": {
"Color": "צבע"
},
@@ -803,6 +842,12 @@
"Color temperature for night mode": {
"Color temperature for night mode": "טמפרטורת צבע למצב לילה"
},
"Color theme for syntax highlighting.": {
"Color theme for syntax highlighting.": ""
},
"Color theme for syntax highlighting. %1 themes available.": {
"Color theme for syntax highlighting. %1 themes available.": ""
},
"Colorful mix of bright contrasting accents.": {
"Colorful mix of bright contrasting accents.": "שילוב צבעוני של דגשים מנוגדים ובהירים."
},
@@ -812,6 +857,9 @@
"Command": {
"Command": "פקודה"
},
"Commands": {
"Commands": ""
},
"Communication": {
"Communication": "תקשורת"
},
@@ -905,6 +953,9 @@
"Control currently playing media": {
"Control currently playing media": "שלוט/שלטי במדיה שמתנגנת כעת"
},
"Control which plugins appear in 'All' mode without requiring a trigger prefix. Drag to reorder.": {
"Control which plugins appear in 'All' mode without requiring a trigger prefix. Drag to reorder.": ""
},
"Control workspaces and columns by scrolling on the bar": {
"Control workspaces and columns by scrolling on the bar": "שלוט/י בסביבות העבודה ועמודות באמצעות גלילה על הסרגל"
},
@@ -926,6 +977,9 @@
"Copy Full Command": {
"Copy Full Command": ""
},
"Copy HTML": {
"Copy HTML": ""
},
"Copy Name": {
"Copy Name": ""
},
@@ -935,6 +989,15 @@
"Copy Process Name": {
"Copy Process Name": "העתק/י שם תהליך"
},
"Copy Text": {
"Copy Text": ""
},
"Copy URL": {
"Copy URL": ""
},
"Copy path": {
"Copy path": ""
},
"Corner Radius": {
"Corner Radius": "רדיוס פינות"
},
@@ -1343,6 +1406,9 @@
"Edge Spacing": {
"Edge Spacing": "ריווח קצוות"
},
"Edit App": {
"Edit App": ""
},
"Education": {
"Education": "חינוך"
},
@@ -1391,6 +1457,9 @@
"Enable loginctl lock integration": {
"Enable loginctl lock integration": "הפעלת האינטגרציה עם loginctl"
},
"Enable media player controls on the lock screen window": {
"Show Media Player": ""
},
"Enable password field display on the lock screen window": {
"Show Password Field": "הצג/י שדה סיסמה"
},
@@ -1427,6 +1496,9 @@
"Enter PIN for ": {
"Enter PIN for ": "הזן/י קוד PIN עבור "
},
"Enter a new name for this workspace": {
"Enter a new name for this workspace": ""
},
"Enter a search query": {
"Enter a search query": "הזן/י שאילתת חיפוש"
},
@@ -1466,6 +1538,9 @@
"Entry unpinned": {
"Entry unpinned": ""
},
"Environment Variables": {
"Environment Variables": ""
},
"Error": {
"Error": "שגיאה"
},
@@ -1481,6 +1556,9 @@
"Exponential": {
"Exponential": "אקספוננציאלי"
},
"Extra Arguments": {
"Extra Arguments": ""
},
"F1/I: Toggle • F10: Help": {
"F1/I: Toggle • F10: Help": "F1/I: החלפה • F10: עזרה"
},
@@ -1670,6 +1748,9 @@
"File Information": {
"File Information": "פרטי קובץ"
},
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": ""
},
"Files": {
"Files": "קבצים"
},
@@ -1868,6 +1949,9 @@
"HSV": {
"HSV": "HSV"
},
"HTML copied to clipboard": {
"HTML copied to clipboard": ""
},
"Health": {
"Health": "בריאות"
},
@@ -1886,9 +1970,18 @@
"Hidden": {
"Hidden": "מוסתר"
},
"Hidden Apps": {
"Hidden Apps": ""
},
"Hidden Network": {
"Hidden Network": "רשת מוסתרת"
},
"Hidden apps won't appear in the launcher. Right-click an app and select 'Hide App' to hide it.": {
"Hidden apps won't appear in the launcher. Right-click an app and select 'Hide App' to hide it.": ""
},
"Hide App": {
"Hide App": ""
},
"Hide Delay": {
"Hide Delay": "עיכוב הסתרה"
},
@@ -1970,6 +2063,9 @@
"IP Address:": {
"IP Address:": "כתובת IP:"
},
"Icon": {
"Icon": ""
},
"Icon Size": {
"Icon Size": "גודל סמל"
},
@@ -2009,6 +2105,9 @@
"Include Transitions": {
"Include Transitions": "כלול/כללי מעברים"
},
"Include desktop actions (shortcuts) in search results.": {
"Include desktop actions (shortcuts) in search results.": ""
},
"Incompatible Plugins Loaded": {
"Incompatible Plugins Loaded": "נטענו תוספים לא תואמים"
},
@@ -2114,11 +2213,18 @@
"Failed to reject pairing": "",
"Failed to ring device": "",
"Failed to send clipboard": "",
"Failed to send file": "",
"Failed to send ping": "",
"Failed to share": "",
"Pairing failed": "",
"Unpair failed": ""
},
"KDE Connect file browser title": {
"Select File to Send": ""
},
"KDE Connect file send": {
"Sending": ""
},
"KDE Connect file share notification": {
"File received from": ""
},
@@ -2184,15 +2290,24 @@
"KDE Connect ring tooltip": {
"Ring": ""
},
"KDE Connect send file button": {
"Send File": ""
},
"KDE Connect service unavailable message": {
"KDE Connect unavailable": ""
},
"KDE Connect share URL button": {
"Share URL": ""
},
"KDE Connect share button": {
"Share Text": ""
},
"KDE Connect share button | KDE Connect share dialog title | KDE Connect share tooltip": {
"Share": ""
},
"KDE Connect share dialog title | KDE Connect share tooltip": {
"Share": ""
},
"KDE Connect share input placeholder": {
"Enter URL or text to share": ""
},
@@ -2265,6 +2380,12 @@
"LED device": {
"LED device": "התקן LED"
},
"Large": {
"Large": ""
},
"Largest": {
"Largest": ""
},
"Last launched %1": {
"Last launched %1": "ההפעלה האחרונה הייתה %1"
},
@@ -2295,6 +2416,9 @@
"Launcher": {
"Launcher": "משגר"
},
"Launcher Button": {
"Launcher Button": ""
},
"Launcher Button Logo": {
"Launcher Button Logo": "סמל כפתור המשגר"
},
@@ -2334,6 +2458,9 @@
"Loading plugins...": {
"Loading plugins...": "טוען תוספים..."
},
"Loading trending...": {
"Loading trending...": ""
},
"Loading...": {
"Loading...": "טוען..."
},
@@ -2709,6 +2836,12 @@
"No adapters": {
"No adapters": "אין מתאמים"
},
"No app customizations.": {
"No app customizations.": ""
},
"No apps found": {
"No apps found": ""
},
"No battery": {
"No battery": "אין סוללה"
},
@@ -2739,15 +2872,24 @@
"No files found": {
"No files found": "לא נמצאו קבצים"
},
"No hidden apps.": {
"No hidden apps.": ""
},
"No items added yet": {
"No items added yet": "לא נוספו פריטים עדיין"
},
"No keybinds found": {
"No keybinds found": "לא נמצאו קיצורי מקלדת"
},
"No launcher plugins installed.": {
"No launcher plugins installed.": ""
},
"No matches": {
"No matches": "אין התאמות"
},
"No plugin results": {
"No plugin results": ""
},
"No plugins found": {
"No plugins found": "לא נמצאו תוספים"
},
@@ -2766,9 +2908,15 @@
"No recent clipboard entries found": {
"No recent clipboard entries found": ""
},
"No results found": {
"No results found": ""
},
"No saved clipboard entries": {
"No saved clipboard entries": ""
},
"No trigger": {
"No trigger": ""
},
"No variants created. Click Add to create a new monitor widget.": {
"No variants created. Click Add to create a new monitor widget.": "לא נוצרו גרסאות. לחץ/י על הוספה כדי ליצור ווידג׳ט תצוגה חדש."
},
@@ -2880,6 +3028,12 @@
"Open Notepad File": {
"Open Notepad File": "פתח/י קובץ בפנקס"
},
"Open folder": {
"Open folder": ""
},
"Open in Browser": {
"Open in Browser": ""
},
"Open search bar to find text": {
"Open search bar to find text": "פתח/י שורת חיפוש כדי למצוא טקסט"
},
@@ -2946,6 +3100,9 @@
"PIN": {
"PIN": "קוד PIN"
},
"Pad Hours": {
"Pad Hours": ""
},
"Padding": {
"Padding": "ריווח פנימי"
},
@@ -3012,6 +3169,9 @@
"Pinned": {
"Pinned": "מוצמד"
},
"Pinned and running apps with drag-and-drop": {
"Pinned and running apps with drag-and-drop": ""
},
"Place plugin directories here. Each plugin should have a plugin.json manifest file.": {
"Place plugin directories here. Each plugin should have a plugin.json manifest file.": "מקם/י כאן את תיקיות התוספים. לכל תוסף צריך להיות קובץ מניפסט בשם plugin.json."
},
@@ -3048,6 +3208,9 @@
"Plugin Management": {
"Plugin Management": "ניהול תוספים"
},
"Plugin Visibility": {
"Plugin Visibility": ""
},
"Plugin is disabled - enable in Plugins settings to use": {
"Plugin is disabled - enable in Plugins settings to use": "התוסף מושבת הפעל/י אותו בהגדרות התוספים כדי להשתמש בו"
},
@@ -3126,6 +3289,9 @@
"Prevent screen timeout": {
"Prevent screen timeout": "מנע/י כיבוי מסך אוטומטי"
},
"Preview": {
"Preview": ""
},
"Primary": {
"Primary": "ראשי"
},
@@ -3240,6 +3406,12 @@
"Remove gaps and border when windows are maximized": {
"Remove gaps and border when windows are maximized": "הסר/י רווחים ומסגרת כאשר חלונות ממוקסמים"
},
"Rename": {
"Rename": ""
},
"Rename Workspace": {
"Rename Workspace": ""
},
"Repeat": {
"Repeat": "חזרה"
},
@@ -3417,6 +3589,12 @@
"Scrolling": {
"Scrolling": "גלילה"
},
"Search App Actions": {
"Search App Actions": ""
},
"Search Options": {
"Search Options": ""
},
"Search by key combo, description, or action name.\\n\\nDefault action copies the keybind to clipboard.\\nRight-click or press Right Arrow to pin frequently used keybinds - they'll appear at the top when not searching.": {
"Search by key combo, description, or action name.\\n\\nDefault action copies the keybind to clipboard.\\nRight-click or press Right Arrow to pin frequently used keybinds - they'll appear at the top when not searching.": "חפש/י לפי צירוף מקשים, תיאור או שם פעולה.\\n\\nפעולת ברירת המחדל מעתיקה את קיצור המקלדת ללוח ההעתקה.\\nלחץ/י לחיצה ימנית או לחץ/י על חץ ימינה כדי להצמיד קיצורים בשימוש תכוף - הם יופיעו בראש הרשימה כשלא מתבצע חיפוש."
},
@@ -3456,6 +3634,9 @@
"Security": {
"Security": "אבטחה"
},
"Select": {
"Select": ""
},
"Select Application": {
"Select Application": "בחר/י אפליקציה"
},
@@ -3534,12 +3715,18 @@
"Setup": {
"Setup": "התקנה"
},
"Share Gamma Control Settings": {
"Share Gamma Control Settings": ""
},
"Shell": {
"Shell": "מעטפת"
},
"Shift+Del: Clear All • Esc: Close": {
"Shift+Del: Clear All • Esc: Close": "Shift+Del: ניקוי הכל • Esc: סגירה"
},
"Shift+Enter to paste": {
"Shift+Enter to paste": ""
},
"Shift+Enter: Paste • Shift+Del: Clear All • Esc: Close": {
"Shift+Enter: Paste • Shift+Del: Clear All • Esc: Close": "Shift+Enter: הדבקה • Shift+Del: ניקוי הכל • Esc: סגירה"
},
@@ -3597,6 +3784,9 @@
"Show Humidity": {
"Show Humidity": "הצג/י לחות"
},
"Show Launcher Button": {
"Show Launcher Button": ""
},
"Show Line Numbers": {
"Show Line Numbers": "הצג/י מספרי שורות"
},
@@ -3774,6 +3964,9 @@
"Sizing": {
"Sizing": "גודל"
},
"Small": {
"Small": ""
},
"Smartcard Authentication": {
"Smartcard Authentication": "אימות עם כרטיס חכם"
},
@@ -4095,12 +4288,24 @@
"Transparency": {
"Transparency": "שקיפות"
},
"Trending GIFs": {
"Trending GIFs": ""
},
"Trending Stickers": {
"Trending Stickers": ""
},
"Trigger": {
"Trigger": "מפעיל"
},
"Trigger Prefix": {
"Trigger Prefix": "קידומת מפעיל"
},
"Trigger: %1": {
"Trigger: %1": ""
},
"Try a different search": {
"Try a different search": ""
},
"Turn off all displays immediately when the lock screen activates": {
"Turn off all displays immediately when the lock screen activates": ""
},
@@ -4110,9 +4315,21 @@
"Type": {
"Type": "סוג"
},
"Type at least 2 characters": {
"Type at least 2 characters": ""
},
"Type this prefix to search keybinds": {
"Type this prefix to search keybinds": "הקלד/י קידומת זו לחיפוש קיצורי מקלדת"
},
"Type to search": {
"Type to search": ""
},
"Type to search apps": {
"Type to search apps": ""
},
"Type to search files": {
"Type to search files": ""
},
"Typography": {
"Typography": "טיפוגרפיה"
},
@@ -4230,6 +4447,9 @@
"Use light theme instead of dark theme": {
"Use light theme instead of dark theme": "השתמש/י בערכת נושא בהירה במקום כהה"
},
"Use meters per second instead of km/h for wind speed": {
"Use meters per second instead of km/h for wind speed": ""
},
"Use smaller notification cards": {
"Use smaller notification cards": "השתמש/י בכרטיסי התראות קטנים יותר"
},
@@ -4257,9 +4477,15 @@
"Username": {
"Username": "שם משתמש"
},
"Uses sunrise/sunset times based on your location.": {
"Uses sunrise/sunset times based on your location.": ""
},
"Uses sunrise/sunset times to automatically adjust night mode based on your location.": {
"Uses sunrise/sunset times to automatically adjust night mode based on your location.": "משתמש בזמני זריחה/שקיעה כדי לכוונן אוטומטית את מצב הלילה (גאמה) לפי המיקום שלך."
},
"Using shared settings from Gamma Control": {
"Using shared settings from Gamma Control": ""
},
"Utilities": {
"Utilities": "כלי עזר"
},
@@ -4460,6 +4686,9 @@
"Wind Speed": {
"Wind Speed": "מהירות הרוח"
},
"Wind Speed in m/s": {
"Wind Speed in m/s": ""
},
"Window Corner Radius": {
"Window Corner Radius": "רדיוס הפינות של החלון"
},
@@ -4493,6 +4722,9 @@
"Workspace Switcher": {
"Workspace Switcher": "מחליף סביבות העבודה"
},
"Workspace name": {
"Workspace name": ""
},
"Workspaces": {
"Workspaces": "סביבות עבודה"
},
@@ -4520,6 +4752,9 @@
"You have unsaved changes. Save before opening a file?": {
"You have unsaved changes. Save before opening a file?": "יש לך שינויים שלא נשמרו. האם ברצונך לשמור לפני פתיחת קובץ?"
},
"actions": {
"actions": ""
},
"apps": {
"apps": "אפליקציות"
},
@@ -4529,6 +4764,12 @@
"bar shadow settings card": {
"Shadow": "צל"
},
"border color": {
"Color": ""
},
"border thickness": {
"Thickness": ""
},
"browse themes button | theme browser header | theme browser window title": {
"Browse Themes": "עיין/י בערכות נושא"
},
@@ -4786,6 +5027,21 @@
"installed status": {
"Installed": "מותקן"
},
"launcher appearance settings": {
"Appearance": ""
},
"launcher border option": {
"Border": ""
},
"launcher footer description": {
"Show mode tabs and keyboard hints at the bottom.": ""
},
"launcher footer visibility": {
"Show Footer": ""
},
"launcher size option": {
"Size": ""
},
"leave empty for default": {
"leave empty for default": "השאר/י ריק לברירת מחדל"
},
@@ -4826,6 +5082,9 @@
"ms": {
"ms": "מילישניות"
},
"nav": {
"nav": ""
},
"no custom theme file status": {
"No custom theme file": "אין קובץ ערכת נושא מותאמת אישית"
},
@@ -4882,6 +5141,12 @@
"official": {
"official": "רשמי"
},
"open": {
"open": ""
},
"outline color": {
"Outline": ""
},
"plugin browser description": {
"Install plugins from the DMS plugin registry": "התקן/י תוספים ממאגר התוספים של DMS"
},
@@ -4897,6 +5162,9 @@
"plugin search placeholder": {
"Search plugins...": "חפש/י תוספים..."
},
"primary color": {
"Primary": ""
},
"process count label in footer": {
"Processes:": ""
},
@@ -4915,6 +5183,9 @@
"registry theme description": {
"Color theme from DMS registry": "ערכת צבעים מהמאגר של DMS"
},
"secondary color": {
"Secondary": ""
},
"seconds": {
"seconds": "שניות"
},
@@ -4928,6 +5199,9 @@
"Surface": "משטח",
"Text": "טקסט"
},
"shadow color option | text color": {
"Text": ""
},
"shadow intensity slider": {
"Intensity": "עוצמה"
},
@@ -5007,6 +5281,9 @@
"wallpaper settings external management": {
"External Wallpaper Management": "ניהול רקעים חיצוני"
},
"weather feels like temperature": {
"Feels Like %1°": ""
},
"wtype not available - install wtype for paste support": {
"wtype not available - install wtype for paste support": "wtype אינו זמין - התקן/י את wtype כדי לאפשר תמיכה בהדבקה"
},

Some files were not shown because too many files have changed in this diff Show More