mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-08 06:28:27 -04:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f971b6bcff | |||
| 0313e96e8e | |||
| c38b161f01 | |||
| acd82589e2 | |||
| 170721a1f1 | |||
| 7e0af52d5b | |||
| e1b6427929 | |||
| 47bf6a8dfd | |||
| 84ac36295f | |||
| ca839487de | |||
| dabbec1dce | |||
| bd4d35d215 | |||
| cfdacd3ea3 |
+164
-86
@@ -449,12 +449,7 @@ func getQuickshellVersionInfo(missingFeatures bool) (string, status, string) {
|
|||||||
func checkDMSInstallation() []checkResult {
|
func checkDMSInstallation() []checkResult {
|
||||||
var results []checkResult
|
var results []checkResult
|
||||||
|
|
||||||
dmsPath := ""
|
dmsPath := resolveDoctorShellPath()
|
||||||
if err := findConfig(nil, nil); err == nil && configPath != "" {
|
|
||||||
dmsPath = configPath
|
|
||||||
} else if path, err := config.LocateDMSConfig(); err == nil {
|
|
||||||
dmsPath = path
|
|
||||||
}
|
|
||||||
|
|
||||||
if dmsPath == "" {
|
if dmsPath == "" {
|
||||||
return []checkResult{{catInstallation, "DMS Configuration", statusError, "Not found", "shell.qml not found in any config path", doctorDocsURL + "#dms-configuration"}}
|
return []checkResult{{catInstallation, "DMS Configuration", statusError, "Not found", "shell.qml not found in any config path", doctorDocsURL + "#dms-configuration"}}
|
||||||
@@ -1167,99 +1162,182 @@ func formatResultsPlain(results []checkResult) string {
|
|||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultDoctorFontFamily = "Inter Variable"
|
||||||
|
defaultDoctorMonoFontFamily = "Fira Code"
|
||||||
|
)
|
||||||
|
|
||||||
|
// bundledFontRelPaths maps settings/default family names to font files shipped with
|
||||||
|
// the shell and loaded via Qt FontLoader (not registered with fontconfig).
|
||||||
|
var bundledFontRelPaths = map[string][]string{
|
||||||
|
"inter variable": {
|
||||||
|
"DankCommon/assets/fonts/inter/InterVariable.ttf",
|
||||||
|
"assets/fonts/inter/InterVariable.ttf",
|
||||||
|
},
|
||||||
|
"fira code": {
|
||||||
|
"DankCommon/assets/fonts/nerd-fonts/FiraCodeNerdFont-Regular.ttf",
|
||||||
|
"assets/fonts/nerd-fonts/FiraCodeNerdFont-Regular.ttf",
|
||||||
|
},
|
||||||
|
"firacode nerd font": {
|
||||||
|
"DankCommon/assets/fonts/nerd-fonts/FiraCodeNerdFont-Regular.ttf",
|
||||||
|
"assets/fonts/nerd-fonts/FiraCodeNerdFont-Regular.ttf",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveDoctorShellPath() string {
|
||||||
|
if err := findConfig(nil, nil); err == nil && configPath != "" {
|
||||||
|
return configPath
|
||||||
|
}
|
||||||
|
if path, err := config.LocateDMSConfig(); err == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func findBundledFontFile(shellPath, family string) string {
|
||||||
|
if shellPath == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
relPaths, ok := bundledFontRelPaths[strings.ToLower(strings.TrimSpace(family))]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for _, rel := range relPaths {
|
||||||
|
path := filepath.Join(shellPath, rel)
|
||||||
|
if info, err := os.Stat(path); err == nil && !info.IsDir() {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBundledDefaultFont(family string) bool {
|
||||||
|
_, ok := bundledFontRelPaths[strings.ToLower(strings.TrimSpace(family))]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func fontInFcList(name, cacheLower string) bool {
|
||||||
|
target := strings.ToLower(strings.TrimSpace(name))
|
||||||
|
if target == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, line := range strings.Split(cacheLower, "\n") {
|
||||||
|
for _, fam := range strings.Split(strings.TrimSpace(line), ",") {
|
||||||
|
if strings.TrimSpace(fam) == target {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkConfiguredFont(label, family, shellPath, fcCache string, fcListAvailable bool, url string) checkResult {
|
||||||
|
if bundled := findBundledFontFile(shellPath, family); bundled != "" {
|
||||||
|
details := "Bundled (Qt FontLoader)"
|
||||||
|
if doctorVerbose {
|
||||||
|
details = bundled
|
||||||
|
}
|
||||||
|
return checkResult{catFonts, label, statusOK, family, details, url}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isBundledDefaultFont(family) {
|
||||||
|
if shellPath == "" {
|
||||||
|
return checkResult{
|
||||||
|
catFonts, label, statusWarn,
|
||||||
|
fmt.Sprintf("'%s' not verified", family),
|
||||||
|
"Could not locate shell config to verify bundled font files.",
|
||||||
|
url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return checkResult{
|
||||||
|
catFonts, label, statusWarn,
|
||||||
|
fmt.Sprintf("'%s' bundled file missing", family),
|
||||||
|
"Expected font file missing from shell install. Reinstall DMS or check DankCommon assets.",
|
||||||
|
url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fcListAvailable {
|
||||||
|
return checkResult{
|
||||||
|
catFonts, label, statusWarn,
|
||||||
|
fmt.Sprintf("'%s' not verified", family),
|
||||||
|
"fc-list not installed; cannot verify custom fonts in fontconfig.",
|
||||||
|
url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fcCache == "" {
|
||||||
|
return checkResult{
|
||||||
|
catFonts, label, statusWarn,
|
||||||
|
fmt.Sprintf("'%s' not found", family),
|
||||||
|
"Fontconfig cache is empty or unreadable. Try running 'fc-cache -fv'.",
|
||||||
|
url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fontInFcList(family, fcCache) {
|
||||||
|
return checkResult{catFonts, label, statusOK, family, "Available via fontconfig", url}
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkResult{
|
||||||
|
catFonts, label, statusWarn,
|
||||||
|
fmt.Sprintf("'%s' not found", family),
|
||||||
|
"Font is not registered with fontconfig. Try running 'fc-cache -fv' or install the font.",
|
||||||
|
url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func checkFonts() []checkResult {
|
func checkFonts() []checkResult {
|
||||||
var results []checkResult
|
var results []checkResult
|
||||||
url := doctorDocsURL + "#fonts"
|
url := doctorDocsURL + "#fonts"
|
||||||
|
|
||||||
configDir, err := os.UserConfigDir()
|
fontFamily := defaultDoctorFontFamily
|
||||||
if err != nil {
|
monoFontFamily := defaultDoctorMonoFontFamily
|
||||||
return nil
|
|
||||||
}
|
|
||||||
settingsPath := filepath.Join(configDir, "DankMaterialShell", "settings.json")
|
|
||||||
|
|
||||||
fontFamily := "Inter Variable"
|
if configDir, err := os.UserConfigDir(); err == nil {
|
||||||
monoFontFamily := "Fira Code"
|
settingsPath := filepath.Join(configDir, "DankMaterialShell", "settings.json")
|
||||||
|
if data, err := os.ReadFile(settingsPath); err == nil {
|
||||||
if data, err := os.ReadFile(settingsPath); err == nil {
|
var settings struct {
|
||||||
var settings struct {
|
FontFamily string `json:"fontFamily"`
|
||||||
FontFamily string `json:"fontFamily"`
|
MonoFontFamily string `json:"monoFontFamily"`
|
||||||
MonoFontFamily string `json:"monoFontFamily"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &settings); err == nil {
|
|
||||||
if settings.FontFamily != "" {
|
|
||||||
fontFamily = settings.FontFamily
|
|
||||||
}
|
}
|
||||||
if settings.MonoFontFamily != "" {
|
if err := json.Unmarshal(data, &settings); err == nil {
|
||||||
monoFontFamily = settings.MonoFontFamily
|
if settings.FontFamily != "" {
|
||||||
}
|
fontFamily = settings.FontFamily
|
||||||
}
|
}
|
||||||
}
|
if settings.MonoFontFamily != "" {
|
||||||
|
monoFontFamily = settings.MonoFontFamily
|
||||||
if !utils.CommandExists("fc-list") {
|
|
||||||
results = append(results, checkResult{catFonts, "Fontconfig Tools", statusWarn, "fc-list not installed", "Cannot verify if fonts are cached.", url})
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve font list
|
|
||||||
output, err := exec.Command("fc-list", ":", "family").Output()
|
|
||||||
if err != nil {
|
|
||||||
results = append(results, checkResult{catFonts, "Fontconfig Cache", statusError, "Failed to query font list", "Fontconfig cache query failed. Try running 'fc-cache -fv'.", url})
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
outStr := string(output)
|
|
||||||
if len(strings.TrimSpace(outStr)) == 0 {
|
|
||||||
results = append(results, checkResult{catFonts, "Fontconfig Cache", statusError, "Cache is empty", "No fonts found in fontconfig cache. Try running 'fc-cache -fv'.", url})
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
lowerFonts := strings.ToLower(outStr)
|
|
||||||
|
|
||||||
// Helper to check if a font exists
|
|
||||||
hasFont := func(name string) bool {
|
|
||||||
target := strings.ToLower(strings.TrimSpace(name))
|
|
||||||
if target == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, line := range strings.Split(lowerFonts, "\n") {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if line == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Each line can have comma-separated families
|
|
||||||
families := strings.Split(line, ",")
|
|
||||||
for _, fam := range families {
|
|
||||||
if strings.TrimSpace(fam) == target {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal Font Check
|
shellPath := resolveDoctorShellPath()
|
||||||
if hasFont(fontFamily) {
|
needFontconfig := !isBundledDefaultFont(fontFamily) || !isBundledDefaultFont(monoFontFamily)
|
||||||
results = append(results, checkResult{catFonts, "Normal Font", statusOK, fontFamily, "Available", url})
|
|
||||||
} else {
|
fcListAvailable := utils.CommandExists("fc-list")
|
||||||
results = append(results, checkResult{
|
fcCache := ""
|
||||||
catFonts, "Normal Font", statusWarn,
|
|
||||||
fmt.Sprintf("'%s' not found", fontFamily),
|
if needFontconfig {
|
||||||
"Font is not registered. Try running 'fc-cache -fv' or install the font.",
|
if !fcListAvailable {
|
||||||
url,
|
results = append(results, checkResult{catFonts, "Fontconfig Tools", statusWarn, "fc-list not installed", "Cannot verify custom fonts in fontconfig cache.", url})
|
||||||
})
|
} else {
|
||||||
|
output, err := exec.Command("fc-list", ":", "family").Output()
|
||||||
|
if err != nil {
|
||||||
|
results = append(results, checkResult{catFonts, "Fontconfig Cache", statusError, "Failed to query font list", "Fontconfig cache query failed. Try running 'fc-cache -fv'.", url})
|
||||||
|
} else {
|
||||||
|
fcCache = strings.ToLower(string(output))
|
||||||
|
if len(strings.TrimSpace(fcCache)) == 0 {
|
||||||
|
results = append(results, checkResult{catFonts, "Fontconfig Cache", statusError, "Cache is empty", "No fonts found in fontconfig cache. Try running 'fc-cache -fv'.", url})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Monospace Font Check
|
results = append(results,
|
||||||
if hasFont(monoFontFamily) {
|
checkConfiguredFont("Normal Font", fontFamily, shellPath, fcCache, fcListAvailable, url),
|
||||||
results = append(results, checkResult{catFonts, "Monospace Font", statusOK, monoFontFamily, "Available", url})
|
checkConfiguredFont("Monospace Font", monoFontFamily, shellPath, fcCache, fcListAvailable, url),
|
||||||
} else {
|
)
|
||||||
results = append(results, checkResult{
|
|
||||||
catFonts, "Monospace Font", statusWarn,
|
|
||||||
fmt.Sprintf("'%s' not found", monoFontFamily),
|
|
||||||
"Font is not registered. Try running 'fc-cache -fv' or install the font.",
|
|
||||||
url,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,37 +6,7 @@ func NewClient() Client {
|
|||||||
geoclueClient, err := newGeoClueClient()
|
geoclueClient, err := newGeoClueClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("GeoClue2 unavailable: %v", err)
|
log.Warnf("GeoClue2 unavailable: %v", err)
|
||||||
return newSeededIpClient()
|
return newIpClient()
|
||||||
}
|
}
|
||||||
|
|
||||||
loc, _ := geoclueClient.GetLocation()
|
|
||||||
if loc.Latitude != 0 || loc.Longitude != 0 {
|
|
||||||
log.Info("Using GeoClue2 location")
|
|
||||||
return geoclueClient
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("GeoClue2 has no fix yet, seeding with IP location")
|
|
||||||
ipLoc, err := fetchIPLocation()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("IP location seed failed: %v", err)
|
|
||||||
return geoclueClient
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Seeded GeoClue2 with IP location")
|
|
||||||
geoclueClient.SeedLocation(Location{Latitude: ipLoc.Latitude, Longitude: ipLoc.Longitude})
|
|
||||||
return geoclueClient
|
return geoclueClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSeededIpClient() *IpClient {
|
|
||||||
client := newIpClient()
|
|
||||||
ipLoc, err := fetchIPLocation()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("IP location also failed: %v", err)
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Using IP location")
|
|
||||||
client.currLocation.Latitude = ipLoc.Latitude
|
|
||||||
client.currLocation.Longitude = ipLoc.Longitude
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const (
|
|||||||
type GeoClueClient struct {
|
type GeoClueClient struct {
|
||||||
currLocation *Location
|
currLocation *Location
|
||||||
locationMutex sync.RWMutex
|
locationMutex sync.RWMutex
|
||||||
|
seedOnce sync.Once
|
||||||
|
|
||||||
dbusConn *dbus.Conn
|
dbusConn *dbus.Conn
|
||||||
clientPath dbus.ObjectPath
|
clientPath dbus.ObjectPath
|
||||||
@@ -230,14 +231,29 @@ func (c *GeoClueClient) SeedLocation(loc Location) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *GeoClueClient) GetLocation() (Location, error) {
|
func (c *GeoClueClient) GetLocation() (Location, error) {
|
||||||
|
loc := c.currentLocation()
|
||||||
|
if loc.Latitude != 0 || loc.Longitude != 0 {
|
||||||
|
return loc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.seedOnce.Do(func() {
|
||||||
|
ipLoc, err := fetchIPLocation()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("GeoClue2 has no fix, IP location seed failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("Seeded GeoClue2 with IP location")
|
||||||
|
c.SeedLocation(Location{Latitude: ipLoc.Latitude, Longitude: ipLoc.Longitude})
|
||||||
|
})
|
||||||
|
|
||||||
|
return c.currentLocation(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GeoClueClient) currentLocation() Location {
|
||||||
c.locationMutex.RLock()
|
c.locationMutex.RLock()
|
||||||
defer c.locationMutex.RUnlock()
|
defer c.locationMutex.RUnlock()
|
||||||
if c.currLocation == nil {
|
if c.currLocation == nil {
|
||||||
return Location{
|
return Location{}
|
||||||
Latitude: 0.0,
|
|
||||||
Longitude: 0.0,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
stateCopy := *c.currLocation
|
return *c.currLocation
|
||||||
return stateCopy, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,24 +8,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func NewManager(client geolocation.Client) (*Manager, error) {
|
func NewManager(client geolocation.Client) (*Manager, error) {
|
||||||
currLocation, err := client.GetLocation()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to get initial location: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
client: client,
|
client: client,
|
||||||
dirty: make(chan struct{}),
|
dirty: make(chan struct{}),
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
|
state: &State{},
|
||||||
state: &State{
|
|
||||||
Latitude: currLocation.Latitude,
|
|
||||||
Longitude: currLocation.Longitude,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.startSignalPump(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.notifierWg.Add(1)
|
m.notifierWg.Add(1)
|
||||||
@@ -34,6 +21,22 @@ func NewManager(client geolocation.Client) (*Manager, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The geolocation client may fetch IP location on first use, so nothing
|
||||||
|
// touches it until a consumer actually asks for location data.
|
||||||
|
func (m *Manager) ensureStarted() {
|
||||||
|
m.startOnce.Do(func() {
|
||||||
|
go func() {
|
||||||
|
currLocation, err := m.client.GetLocation()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to get initial location: %v", err)
|
||||||
|
} else {
|
||||||
|
m.handleLocationChange(currLocation)
|
||||||
|
}
|
||||||
|
m.startSignalPump()
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) Close() {
|
func (m *Manager) Close() {
|
||||||
close(m.stopChan)
|
close(m.stopChan)
|
||||||
m.notifierWg.Wait()
|
m.notifierWg.Wait()
|
||||||
@@ -48,6 +51,7 @@ func (m *Manager) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Subscribe(id string) chan State {
|
func (m *Manager) Subscribe(id string) chan State {
|
||||||
|
m.ensureStarted()
|
||||||
ch := make(chan State, 64)
|
ch := make(chan State, 64)
|
||||||
m.subscribers.Store(id, ch)
|
m.subscribers.Store(id, ch)
|
||||||
return ch
|
return ch
|
||||||
@@ -59,7 +63,7 @@ func (m *Manager) Unsubscribe(id string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) startSignalPump() error {
|
func (m *Manager) startSignalPump() {
|
||||||
m.sigWG.Add(1)
|
m.sigWG.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer m.sigWG.Done()
|
defer m.sigWG.Done()
|
||||||
@@ -80,8 +84,6 @@ func (m *Manager) startSignalPump() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) handleLocationChange(location geolocation.Location) {
|
func (m *Manager) handleLocationChange(location geolocation.Location) {
|
||||||
@@ -102,6 +104,7 @@ func (m *Manager) notifySubscribers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) GetState() State {
|
func (m *Manager) GetState() State {
|
||||||
|
m.ensureStarted()
|
||||||
m.stateMutex.RLock()
|
m.stateMutex.RLock()
|
||||||
defer m.stateMutex.RUnlock()
|
defer m.stateMutex.RUnlock()
|
||||||
if m.state == nil {
|
if m.state == nil {
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ type Manager struct {
|
|||||||
state *State
|
state *State
|
||||||
stateMutex sync.RWMutex
|
stateMutex sync.RWMutex
|
||||||
|
|
||||||
client geolocation.Client
|
client geolocation.Client
|
||||||
|
startOnce sync.Once
|
||||||
|
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
sigWG sync.WaitGroup
|
sigWG sync.WaitGroup
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ Singleton {
|
|||||||
function openModal(modal) {
|
function openModal(modal) {
|
||||||
PopoutManager.screenshotActive = false;
|
PopoutManager.screenshotActive = false;
|
||||||
const screenName = modal.effectiveScreen?.name ?? "unknown";
|
const screenName = modal.effectiveScreen?.name ?? "unknown";
|
||||||
currentModalsByScreen[screenName] = modal;
|
var next = {};
|
||||||
|
for (var k in currentModalsByScreen)
|
||||||
|
next[k] = currentModalsByScreen[k];
|
||||||
|
next[screenName] = modal;
|
||||||
|
currentModalsByScreen = next;
|
||||||
modalChanged();
|
modalChanged();
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
if (!modal.allowStacking)
|
if (!modal.allowStacking)
|
||||||
@@ -34,7 +38,12 @@ Singleton {
|
|||||||
function closeModal(modal) {
|
function closeModal(modal) {
|
||||||
const screenName = modal.effectiveScreen?.name ?? "unknown";
|
const screenName = modal.effectiveScreen?.name ?? "unknown";
|
||||||
if (currentModalsByScreen[screenName] === modal) {
|
if (currentModalsByScreen[screenName] === modal) {
|
||||||
delete currentModalsByScreen[screenName];
|
var next = {};
|
||||||
|
for (var k in currentModalsByScreen) {
|
||||||
|
if (k !== screenName)
|
||||||
|
next[k] = currentModalsByScreen[k];
|
||||||
|
}
|
||||||
|
currentModalsByScreen = next;
|
||||||
modalChanged();
|
modalChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -436,6 +436,10 @@ Singleton {
|
|||||||
|
|
||||||
function getMatugenColor(path, fallback) {
|
function getMatugenColor(path, fallback) {
|
||||||
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark";
|
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark";
|
||||||
|
return getMatugenColorForMode(colorMode, path, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMatugenColorForMode(colorMode, path, fallback) {
|
||||||
let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode];
|
let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode];
|
||||||
for (const part of path.split(".")) {
|
for (const part of path.split(".")) {
|
||||||
if (!cur || typeof cur !== "object" || !(part in cur))
|
if (!cur || typeof cur !== "object" || !(part in cur))
|
||||||
@@ -445,6 +449,82 @@ Singleton {
|
|||||||
return cur || fallback;
|
return cur || fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractCurrentTheme(themeName) {
|
||||||
|
var name = themeName || "Extracted Theme";
|
||||||
|
var dark = {};
|
||||||
|
var light = {};
|
||||||
|
|
||||||
|
if (currentTheme === dynamic) {
|
||||||
|
dark = buildExtractedDynamicMode("dark", name + " Dark");
|
||||||
|
light = buildExtractedDynamicMode("light", name + " Light");
|
||||||
|
} else if (currentTheme === custom && customThemeRawData) {
|
||||||
|
var rawDark = customThemeRawData.dark || null;
|
||||||
|
var rawLight = customThemeRawData.light || null;
|
||||||
|
if (rawDark) {
|
||||||
|
dark = JSON.parse(JSON.stringify(rawDark));
|
||||||
|
if (!dark.name)
|
||||||
|
dark.name = name + " Dark";
|
||||||
|
} else if (rawLight) {
|
||||||
|
dark = buildExtractedDynamicMode("dark", name + " Dark");
|
||||||
|
} else {
|
||||||
|
dark = currentThemeData ? JSON.parse(JSON.stringify(currentThemeData)) : {};
|
||||||
|
dark.name = name + " Dark";
|
||||||
|
}
|
||||||
|
if (rawLight) {
|
||||||
|
light = JSON.parse(JSON.stringify(rawLight));
|
||||||
|
if (!light.name)
|
||||||
|
light.name = name + " Light";
|
||||||
|
} else if (rawDark) {
|
||||||
|
light = buildExtractedDynamicMode("light", name + " Light");
|
||||||
|
} else {
|
||||||
|
light = currentThemeData ? JSON.parse(JSON.stringify(currentThemeData)) : {};
|
||||||
|
light.name = name + " Light";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var darkTheme = StockThemes.getThemeByName(currentTheme, false);
|
||||||
|
var lightTheme = StockThemes.getThemeByName(currentTheme, true);
|
||||||
|
dark = darkTheme ? JSON.parse(JSON.stringify(darkTheme)) : {};
|
||||||
|
light = lightTheme ? JSON.parse(JSON.stringify(lightTheme)) : {};
|
||||||
|
dark.name = name + " Dark";
|
||||||
|
light.name = name + " Light";
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
dark: dark,
|
||||||
|
light: light
|
||||||
|
}, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildExtractedDynamicMode(colorMode, name) {
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"primary": getMatugenColorForMode(colorMode, "primary", "#42a5f5"),
|
||||||
|
"primaryText": getMatugenColorForMode(colorMode, "on_primary", "#ffffff"),
|
||||||
|
"primaryContainer": getMatugenColorForMode(colorMode, "primary_container", "#1976d2"),
|
||||||
|
"secondary": getMatugenColorForMode(colorMode, "secondary", "#8ab4f8"),
|
||||||
|
"secondaryContainer": getMatugenColorForMode(colorMode, "secondary_container", getMatugenColorForMode(colorMode, "surface_container_high", "#292b2f")),
|
||||||
|
"tertiary": getMatugenColorForMode(colorMode, "tertiary", "#efb8c8"),
|
||||||
|
"tertiaryContainer": getMatugenColorForMode(colorMode, "tertiary_container", getMatugenColorForMode(colorMode, "surface_container_high", "#292b2f")),
|
||||||
|
"surface": getMatugenColorForMode(colorMode, "surface", "#1a1c1e"),
|
||||||
|
"surfaceText": getMatugenColorForMode(colorMode, "on_background", "#e3e8ef"),
|
||||||
|
"surfaceVariant": getMatugenColorForMode(colorMode, "surface_variant", "#44464f"),
|
||||||
|
"surfaceVariantText": getMatugenColorForMode(colorMode, "on_surface_variant", "#c4c7c5"),
|
||||||
|
"surfaceTint": getMatugenColorForMode(colorMode, "surface_tint", "#8ab4f8"),
|
||||||
|
"background": getMatugenColorForMode(colorMode, "background", "#1a1c1e"),
|
||||||
|
"backgroundText": getMatugenColorForMode(colorMode, "on_background", "#e3e8ef"),
|
||||||
|
"outline": getMatugenColorForMode(colorMode, "outline", "#8e918f"),
|
||||||
|
"surfaceContainerLowest": getMatugenColorForMode(colorMode, "surface_container_lowest", "#0e1013"),
|
||||||
|
"surfaceContainerLow": getMatugenColorForMode(colorMode, "surface_container_low", "#181a1d"),
|
||||||
|
"surfaceContainer": getMatugenColorForMode(colorMode, "surface_container", "#1e2023"),
|
||||||
|
"surfaceContainerHigh": getMatugenColorForMode(colorMode, "surface_container_high", "#292b2f"),
|
||||||
|
"surfaceContainerHighest": getMatugenColorForMode(colorMode, "surface_container_highest", "#343740"),
|
||||||
|
"error": getMatugenColorForMode(colorMode, "error", "#F2B8B5"),
|
||||||
|
"warning": "#FF9800",
|
||||||
|
"info": "#2196F3",
|
||||||
|
"success": "#4CAF50"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
readonly property var currentThemeData: {
|
readonly property var currentThemeData: {
|
||||||
if (currentTheme === "custom") {
|
if (currentTheme === "custom") {
|
||||||
return customThemeData || StockThemes.getThemeByName("purple", isLightMode);
|
return customThemeData || StockThemes.getThemeByName("purple", isLightMode);
|
||||||
|
|||||||
@@ -22,6 +22,18 @@ Singleton {
|
|||||||
activeTrayMenus = newMenus
|
activeTrayMenus = newMenus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeHoverMenus() {
|
||||||
|
for (const screenName in activeTrayMenus) {
|
||||||
|
const menu = activeTrayMenus[screenName]
|
||||||
|
if (!menu || menu.openedByHover !== true) continue
|
||||||
|
if (typeof menu.close === "function") {
|
||||||
|
menu.close()
|
||||||
|
} else if (menu.showMenu !== undefined) {
|
||||||
|
menu.showMenu = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function closeAllMenus() {
|
function closeAllMenus() {
|
||||||
for (const screenName in activeTrayMenus) {
|
for (const screenName in activeTrayMenus) {
|
||||||
const menu = activeTrayMenus[screenName]
|
const menu = activeTrayMenus[screenName]
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Item {
|
|||||||
|
|
||||||
property string source: ""
|
property string source: ""
|
||||||
property int glyphSize: 14
|
property int glyphSize: 14
|
||||||
|
property bool badgeVisible: true
|
||||||
|
|
||||||
readonly property var sourceAsset: ({
|
readonly property var sourceAsset: ({
|
||||||
"flatpak": "../../assets/package-sources/flatpak.svg",
|
"flatpak": "../../assets/package-sources/flatpak.svg",
|
||||||
@@ -17,7 +18,7 @@ Item {
|
|||||||
|
|
||||||
readonly property string assetPath: sourceAsset[source] || ""
|
readonly property string assetPath: sourceAsset[source] || ""
|
||||||
|
|
||||||
visible: SettingsData.dankLauncherV2ShowSourceBadges && assetPath.length > 0
|
visible: badgeVisible && SettingsData.dankLauncherV2ShowSourceBadges && assetPath.length > 0
|
||||||
implicitWidth: glyphSize
|
implicitWidth: glyphSize
|
||||||
implicitHeight: glyphSize
|
implicitHeight: glyphSize
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ Rectangle {
|
|||||||
anchors.margins: Theme.spacingXS
|
anchors.margins: Theme.spacingXS
|
||||||
source: root.item?.type === "app" ? (root.item.source || "") : ""
|
source: root.item?.type === "app" ? (root.item.source || "") : ""
|
||||||
glyphSize: 16
|
glyphSize: 16
|
||||||
visible: !root.isSelected && !!source
|
badgeVisible: !root.isSelected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -609,7 +609,7 @@ Item {
|
|||||||
_closeHoverNotepad();
|
_closeHoverNotepad();
|
||||||
activeHoverTrigger = "";
|
activeHoverTrigger = "";
|
||||||
PopoutManager.dismissHoverPopoutForScreen(barWindow?.screen);
|
PopoutManager.dismissHoverPopoutForScreen(barWindow?.screen);
|
||||||
TrayMenuManager.closeAllMenus();
|
TrayMenuManager.closeHoverMenus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function _beginSupersededCloseForActive() {
|
function _beginSupersededCloseForActive() {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ BasePill {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.MiddleButton
|
acceptedButtons: Qt.MiddleButton
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: mouse => {
|
onPressed: mouse => {
|
||||||
root.triggerRipple(this, mouse.x, mouse.y);
|
root.triggerRipple(this, mouse.x, mouse.y);
|
||||||
SessionData.setDoNotDisturb(!SessionData.doNotDisturb);
|
SessionData.setDoNotDisturb(!SessionData.doNotDisturb);
|
||||||
|
|||||||
@@ -1484,6 +1484,7 @@ BasePill {
|
|||||||
property bool isVertical: false
|
property bool isVertical: false
|
||||||
property var axis: null
|
property var axis: null
|
||||||
property bool showMenu: false
|
property bool showMenu: false
|
||||||
|
property bool openedByHover: false
|
||||||
property var menuHandle: null
|
property var menuHandle: null
|
||||||
|
|
||||||
ListModel {
|
ListModel {
|
||||||
@@ -1493,7 +1494,8 @@ BasePill {
|
|||||||
return entryStack.count ? entryStack.get(entryStack.count - 1).handle : null;
|
return entryStack.count ? entryStack.get(entryStack.count - 1).handle : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) {
|
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj, byHover) {
|
||||||
|
openedByHover = byHover === true;
|
||||||
trayItem = item;
|
trayItem = item;
|
||||||
anchorItem = anchor;
|
anchorItem = anchor;
|
||||||
parentScreen = screen;
|
parentScreen = screen;
|
||||||
@@ -2084,7 +2086,7 @@ BasePill {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) {
|
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj, byHover) {
|
||||||
if (!screen)
|
if (!screen)
|
||||||
return;
|
return;
|
||||||
if (currentTrayMenu) {
|
if (currentTrayMenu) {
|
||||||
@@ -2099,7 +2101,7 @@ BasePill {
|
|||||||
currentTrayMenu = trayMenuComponent.createObject(null);
|
currentTrayMenu = trayMenuComponent.createObject(null);
|
||||||
if (!currentTrayMenu)
|
if (!currentTrayMenu)
|
||||||
return;
|
return;
|
||||||
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj);
|
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj, byHover === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _trayLayoutRoot() {
|
function _trayLayoutRoot() {
|
||||||
@@ -2147,7 +2149,7 @@ BasePill {
|
|||||||
if (!hit?.trayItem?.hasMenu)
|
if (!hit?.trayItem?.hasMenu)
|
||||||
return false;
|
return false;
|
||||||
const anchor = hit.children?.length > 0 ? hit.children[0] : hit;
|
const anchor = hit.children?.length > 0 ? hit.children[0] : hit;
|
||||||
showForTrayItem(hit.trayItem, anchor, parentScreen, isAtBottom, isVerticalOrientation, axis);
|
showForTrayItem(hit.trayItem, anchor, parentScreen, isAtBottom, isVerticalOrientation, axis, true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,17 +302,22 @@ Singleton {
|
|||||||
function findConfigEntryByFingerprint(data, outputIdentifiers, autoOnly) {
|
function findConfigEntryByFingerprint(data, outputIdentifiers, autoOnly) {
|
||||||
const targetKey = outputSetFingerprint(outputIdentifiers);
|
const targetKey = outputSetFingerprint(outputIdentifiers);
|
||||||
const configs = data.configurations || [];
|
const configs = data.configurations || [];
|
||||||
|
let firstUnnamed = null;
|
||||||
for (let i = 0; i < configs.length; i++) {
|
for (let i = 0; i < configs.length; i++) {
|
||||||
if (configFingerprint(configs[i]) === targetKey) {
|
if (configFingerprint(configs[i]) !== targetKey)
|
||||||
if (autoOnly && configs[i].name)
|
continue;
|
||||||
continue;
|
if (configs[i].name && !autoOnly)
|
||||||
return {
|
return {
|
||||||
entry: configs[i],
|
entry: configs[i],
|
||||||
index: i
|
index: i
|
||||||
};
|
};
|
||||||
}
|
if (!configs[i].name && !firstUnnamed)
|
||||||
|
firstUnnamed = {
|
||||||
|
entry: configs[i],
|
||||||
|
index: i
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return firstUnnamed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProfileMonitorInclusion(profileId) {
|
function getProfileMonitorInclusion(profileId) {
|
||||||
@@ -751,7 +756,7 @@ Singleton {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
readMonitorsJson(data => {
|
readMonitorsJson(data => {
|
||||||
const match = findConfigEntryByFingerprint(data, currentOutputSet, true);
|
const match = findConfigEntryByFingerprint(data, currentOutputSet, false);
|
||||||
if (match) {
|
if (match) {
|
||||||
if (configEntryMatchesLiveLayout(match.entry)) {
|
if (configEntryMatchesLiveLayout(match.entry)) {
|
||||||
SettingsData.setActiveDisplayProfile(CompositorService.compositor, match.entry.id);
|
SettingsData.setActiveDisplayProfile(CompositorService.compositor, match.entry.id);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtCore
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.FileBrowser
|
import qs.Modals.FileBrowser
|
||||||
@@ -13,6 +14,7 @@ Item {
|
|||||||
id: themeColorsTab
|
id: themeColorsTab
|
||||||
|
|
||||||
property var parentModal: null
|
property var parentModal: null
|
||||||
|
property string pendingExtractJson: ""
|
||||||
readonly property bool connectedFrameModeActive: SettingsData.connectedFrameModeActive
|
readonly property bool connectedFrameModeActive: SettingsData.connectedFrameModeActive
|
||||||
readonly property bool frameModeActive: SettingsData.frameEnabled
|
readonly property bool frameModeActive: SettingsData.frameEnabled
|
||||||
property var cachedIconThemes: SettingsData.availableIconThemes
|
property var cachedIconThemes: SettingsData.availableIconThemes
|
||||||
@@ -564,7 +566,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width - 120 - Theme.spacingM
|
width: parent.width - 120 - Theme.spacingM - 36 - Theme.spacingM
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
@@ -586,6 +588,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
id: wallpaperPathText
|
||||||
text: {
|
text: {
|
||||||
if (ToastService.wallpaperErrorStatus === "error")
|
if (ToastService.wallpaperErrorStatus === "error")
|
||||||
return I18n.tr("Wallpaper processing failed", "wallpaper processing error");
|
return I18n.tr("Wallpaper processing failed", "wallpaper processing error");
|
||||||
@@ -603,6 +606,22 @@ Item {
|
|||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
buttonSize: 36
|
||||||
|
iconName: "download"
|
||||||
|
iconSize: Theme.iconSize
|
||||||
|
backgroundColor: Theme.primaryHover
|
||||||
|
iconColor: Theme.primary
|
||||||
|
tooltipText: I18n.tr("Extract theme to JSON", "extract theme tooltip")
|
||||||
|
anchors.bottom: wallpaperPathText.bottom
|
||||||
|
onClicked: {
|
||||||
|
pendingExtractJson = Theme.extractCurrentTheme();
|
||||||
|
saveBrowserLoader.active = true;
|
||||||
|
if (saveBrowserLoader.item)
|
||||||
|
saveBrowserLoader.item.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsDropdownRow {
|
SettingsDropdownRow {
|
||||||
@@ -2977,6 +2996,28 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LazyLoader {
|
||||||
|
id: saveBrowserLoader
|
||||||
|
active: false
|
||||||
|
|
||||||
|
FileBrowserSurfaceModal {
|
||||||
|
id: saveBrowser
|
||||||
|
|
||||||
|
browserTitle: I18n.tr("Save Extracted Theme", "extract theme save dialog title")
|
||||||
|
browserIcon: "download"
|
||||||
|
browserType: "default"
|
||||||
|
fileExtensions: ["*.json"]
|
||||||
|
allowStacking: true
|
||||||
|
saveMode: true
|
||||||
|
defaultFileName: "dms-extracted-theme.json"
|
||||||
|
|
||||||
|
onFileSelected: path => {
|
||||||
|
saveExtractedTheme(pendingExtractJson, Paths.strip(path));
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LazyLoader {
|
LazyLoader {
|
||||||
id: themeBrowserLoader
|
id: themeBrowserLoader
|
||||||
active: false
|
active: false
|
||||||
@@ -2992,4 +3033,26 @@ Item {
|
|||||||
if (themeBrowserLoader.item)
|
if (themeBrowserLoader.item)
|
||||||
themeBrowserLoader.item.show();
|
themeBrowserLoader.item.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: extractSaveFileView
|
||||||
|
blockWrites: true
|
||||||
|
preload: false
|
||||||
|
atomicWrites: true
|
||||||
|
printErrors: true
|
||||||
|
|
||||||
|
onSaved: {
|
||||||
|
ToastService.showInfo(I18n.tr("Theme extracted to: %1", "extract theme success").arg(Paths.strip(extractSaveFileView.path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
onSaveFailed: error => {
|
||||||
|
ToastService.showError(I18n.tr("Failed to extract theme", "extract theme error"));
|
||||||
|
log.warn("Failed to write extracted theme to " + extractSaveFileView.path + ": " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveExtractedTheme(json, outputPath) {
|
||||||
|
extractSaveFileView.path = outputPath;
|
||||||
|
extractSaveFileView.setText(json);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ Column {
|
|||||||
id: sharedTooltip
|
id: sharedTooltip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: sharedTooltip.hide()
|
||||||
|
|
||||||
signal itemEnabledChanged(string sectionId, string itemId, bool enabled)
|
signal itemEnabledChanged(string sectionId, string itemId, bool enabled)
|
||||||
signal itemOrderChanged(string sectionId, var orderedIds)
|
signal itemOrderChanged(string sectionId, var orderedIds)
|
||||||
signal addWidget(string sectionId)
|
signal addWidget(string sectionId)
|
||||||
@@ -1215,6 +1217,7 @@ Column {
|
|||||||
iconSize: 18
|
iconSize: 18
|
||||||
iconColor: Theme.error
|
iconColor: Theme.error
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
sharedTooltip.hide();
|
||||||
root.removeWidget(root.sectionId, index);
|
root.removeWidget(root.sectionId, index);
|
||||||
}
|
}
|
||||||
onEntered: {
|
onEntered: {
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ Singleton {
|
|||||||
|
|
||||||
function sendAlert(title, message, isWarning, category, notificationType) {
|
function sendAlert(title, message, isWarning, category, notificationType) {
|
||||||
if (notificationType === 1) {
|
if (notificationType === 1) {
|
||||||
Quickshell.execDetached(["notify-send", "-u", isWarning ? "critical" : "normal", "-a", "DMS", "-i", isWarning ? "battery-caution" : "battery-charging", title, message]);
|
Quickshell.execDetached(["notify-send", "-u", isWarning ? "critical" : "normal", "-a", "DMS", "-i", isWarning ? "material:battery_alert" : "material:battery_charging_full", title, message]);
|
||||||
} else {
|
} else {
|
||||||
if (isWarning) {
|
if (isWarning) {
|
||||||
ToastService.showWarning(title, message, "", category);
|
ToastService.showWarning(title, message, "", category);
|
||||||
@@ -157,7 +157,7 @@ Singleton {
|
|||||||
if (isCharging && batteryLevel >= SettingsData.batteryChargeLimit) {
|
if (isCharging && batteryLevel >= SettingsData.batteryChargeLimit) {
|
||||||
if (!_hasNotifiedChargeLimit && SettingsData.batteryNotifyChargeLimit) {
|
if (!_hasNotifiedChargeLimit && SettingsData.batteryNotifyChargeLimit) {
|
||||||
_hasNotifiedChargeLimit = true;
|
_hasNotifiedChargeLimit = true;
|
||||||
sendAlert(I18n.tr("Charge Limit Reached"), I18n.tr("Battery has charged to your set limit of %1%").arg(SettingsData.batteryChargeLimit), false, "battery-charge-limit", SettingsData.batteryChargeLimitNotificationType);
|
sendAlert(I18n.tr("Charge Limit Reached"), I18n.tr("Battery has charged to your set limit of %1%").arg(SettingsData.batteryChargeLimit), false, "material:battery_profile", SettingsData.batteryChargeLimitNotificationType);
|
||||||
}
|
}
|
||||||
} else if (!isCharging || batteryLevel < SettingsData.batteryChargeLimit - 2) {
|
} else if (!isCharging || batteryLevel < SettingsData.batteryChargeLimit - 2) {
|
||||||
_hasNotifiedChargeLimit = false;
|
_hasNotifiedChargeLimit = false;
|
||||||
@@ -173,7 +173,7 @@ Singleton {
|
|||||||
if (isCriticalBattery) {
|
if (isCriticalBattery) {
|
||||||
if (!_hasNotifiedCriticalBattery && SettingsData.batteryNotifyCritical) {
|
if (!_hasNotifiedCriticalBattery && SettingsData.batteryNotifyCritical) {
|
||||||
_hasNotifiedCriticalBattery = true;
|
_hasNotifiedCriticalBattery = true;
|
||||||
sendAlert(I18n.tr("Critical Battery"), I18n.tr("Battery is at %1% - Connect charger immediately!").arg(batteryLevel), true, "battery-critical", SettingsData.batteryCriticalNotificationType);
|
sendAlert(I18n.tr("Critical Battery"), I18n.tr("Battery is at %1% - Connect charger immediately!").arg(batteryLevel), true, "material:battery_alert", SettingsData.batteryCriticalNotificationType);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -186,7 +186,7 @@ Singleton {
|
|||||||
if (isLowBattery) {
|
if (isLowBattery) {
|
||||||
if (!_hasNotifiedLowBattery && SettingsData.batteryNotifyLow) {
|
if (!_hasNotifiedLowBattery && SettingsData.batteryNotifyLow) {
|
||||||
_hasNotifiedLowBattery = true;
|
_hasNotifiedLowBattery = true;
|
||||||
sendAlert(I18n.tr("Low Battery"), I18n.tr("Battery is at %1% - Consider charging soon").arg(batteryLevel), true, "battery-low", SettingsData.batteryLowNotificationType);
|
sendAlert(I18n.tr("Low Battery"), I18n.tr("Battery is at %1% - Consider charging soon").arg(batteryLevel), true, "material:battery_0_bar", SettingsData.batteryLowNotificationType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SettingsData.batteryAutoPowerSaver && PowerProfileWatcher.available) {
|
if (SettingsData.batteryAutoPowerSaver && PowerProfileWatcher.available) {
|
||||||
@@ -230,6 +230,27 @@ Singleton {
|
|||||||
|
|
||||||
applyPowerProfile();
|
applyPowerProfile();
|
||||||
|
|
||||||
|
if (isPluggedIn) {
|
||||||
|
const dismissLow = SettingsData.batteryLowNotificationType === 1 && SettingsData.notificationTimeoutNormal === 0;
|
||||||
|
const dismissCritical = SettingsData.batteryCriticalNotificationType === 1 && SettingsData.notificationTimeoutCritical === 0;
|
||||||
|
|
||||||
|
if (dismissLow || dismissCritical) {
|
||||||
|
const lowSummary = I18n.tr("Low Battery");
|
||||||
|
const criticalSummary = I18n.tr("Critical Battery");
|
||||||
|
|
||||||
|
for (const w of NotificationService.visibleNotifications) {
|
||||||
|
if (!w || !w.notification)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const summary = w.notification.summary;
|
||||||
|
|
||||||
|
if ((dismissLow && summary === lowSummary) || (dismissCritical && summary === criticalSummary)) {
|
||||||
|
NotificationService.dismissNotification(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
previousPluggedState = isPluggedIn;
|
previousPluggedState = isPluggedIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -388,6 +388,8 @@ Singleton {
|
|||||||
const binds = bindsData[cat];
|
const binds = bindsData[cat];
|
||||||
for (var i = 0; i < binds.length; i++) {
|
for (var i = 0; i < binds.length; i++) {
|
||||||
const bind = binds[i];
|
const bind = binds[i];
|
||||||
|
if (currentProvider === "hyprland" && bind.action && bind.action.startsWith("exec "))
|
||||||
|
bind.action = "spawn " + bind.action.slice(5);
|
||||||
const targetCat = Actions.isDmsAction(bind.action) ? "DMS" : cat;
|
const targetCat = Actions.isDmsAction(bind.action) ? "DMS" : cat;
|
||||||
if (!processed[targetCat])
|
if (!processed[targetCat])
|
||||||
processed[targetCat] = [];
|
processed[targetCat] = [];
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isIdle(player: MprisPlayer): bool {
|
function isIdle(player: MprisPlayer): bool {
|
||||||
return player && player.playbackState === MprisPlaybackState.Stopped && !player.trackTitle && !player.trackArtist;
|
return player && player.playbackState === MprisPlaybackState.Stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Known "<title> | <App>" suffixes stripped for matching only; display keeps the full title
|
// Known "<title> | <App>" suffixes stripped for matching only; display keeps the full title
|
||||||
|
|||||||
@@ -105,9 +105,11 @@ PanelWindow {
|
|||||||
|
|
||||||
readonly property bool slideoutBlurActive: root.visible && BlurService.enabled && Theme.connectedSurfaceBlurEnabled
|
readonly property bool slideoutBlurActive: root.visible && BlurService.enabled && Theme.connectedSurfaceBlurEnabled
|
||||||
|
|
||||||
|
readonly property string _slideoutScreenName: modelData?.name ?? ""
|
||||||
|
|
||||||
WlrLayershell.layer: (!suppressOverlayLayer && (triggerUsesOverlayLayer || CompositorService.framePeerSurfacesUseOverlayForScreen(modelData))) ? WlrLayershell.Overlay : WlrLayershell.Top
|
WlrLayershell.layer: (!suppressOverlayLayer && (triggerUsesOverlayLayer || CompositorService.framePeerSurfacesUseOverlayForScreen(modelData))) ? WlrLayershell.Overlay : WlrLayershell.Top
|
||||||
WlrLayershell.exclusiveZone: 0
|
WlrLayershell.exclusiveZone: 0
|
||||||
WlrLayershell.keyboardFocus: isVisible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
WlrLayershell.keyboardFocus: isVisible && !ModalManager.currentModalsByScreen[_slideoutScreenName] ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||||
|
|
||||||
readonly property real dpr: CompositorService.getScreenScale(root.screen)
|
readonly property real dpr: CompositorService.getScreenScale(root.screen)
|
||||||
readonly property real alignedWidth: Theme.px(expandable && expandedWidth ? expandedWidthValue : slideoutWidth, dpr)
|
readonly property real alignedWidth: Theme.px(expandable && expandedWidth ? expandedWidthValue : slideoutWidth, dpr)
|
||||||
|
|||||||
@@ -869,7 +869,7 @@ Item {
|
|||||||
|
|
||||||
readonly property var tooltipTexts: ({
|
readonly property var tooltipTexts: ({
|
||||||
"dms": I18n.tr("DMS shell actions (launcher, clipboard, etc.)"),
|
"dms": I18n.tr("DMS shell actions (launcher, clipboard, etc.)"),
|
||||||
"compositor": I18n.tr("Niri compositor actions (focus, move, etc.)"),
|
"compositor": I18n.tr("Compositor actions (focus, move, etc.)", "keybind action type tooltip"),
|
||||||
"spawn": I18n.tr("Run a program (e.g., firefox, kitty)"),
|
"spawn": I18n.tr("Run a program (e.g., firefox, kitty)"),
|
||||||
"shell": I18n.tr("Run a shell command (e.g., notify-send)")
|
"shell": I18n.tr("Run a shell command (e.g., notify-send)")
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user