mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-13 14:36:32 -04:00
fix(fonts): auto-rebuild font cache when configured fonts are missing
- Add Fonts category to dms doctor for manual diagnostics - Fix a default font setting warning
This commit is contained in:
@@ -125,6 +125,7 @@ const (
|
|||||||
catConfigFiles
|
catConfigFiles
|
||||||
catServices
|
catServices
|
||||||
catEnvironment
|
catEnvironment
|
||||||
|
catFonts
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c category) String() string {
|
func (c category) String() string {
|
||||||
@@ -147,6 +148,8 @@ func (c category) String() string {
|
|||||||
return "Services"
|
return "Services"
|
||||||
case catEnvironment:
|
case catEnvironment:
|
||||||
return "Environment"
|
return "Environment"
|
||||||
|
case catFonts:
|
||||||
|
return "Fonts"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
@@ -213,6 +216,7 @@ func runDoctor(cmd *cobra.Command, args []string) {
|
|||||||
checkConfigurationFiles(),
|
checkConfigurationFiles(),
|
||||||
checkSystemdServices(),
|
checkSystemdServices(),
|
||||||
checkEnvironmentVars(),
|
checkEnvironmentVars(),
|
||||||
|
checkFonts(),
|
||||||
)
|
)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@@ -1135,3 +1139,100 @@ func formatResultsPlain(results []checkResult) string {
|
|||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkFonts() []checkResult {
|
||||||
|
var results []checkResult
|
||||||
|
url := doctorDocsURL + "#fonts"
|
||||||
|
|
||||||
|
configDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
settingsPath := filepath.Join(configDir, "DankMaterialShell", "settings.json")
|
||||||
|
|
||||||
|
fontFamily := "Inter Variable"
|
||||||
|
monoFontFamily := "Fira Code"
|
||||||
|
|
||||||
|
if data, err := os.ReadFile(settingsPath); err == nil {
|
||||||
|
var settings struct {
|
||||||
|
FontFamily string `json:"fontFamily"`
|
||||||
|
MonoFontFamily string `json:"monoFontFamily"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &settings); err == nil {
|
||||||
|
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
|
||||||
|
if hasFont(fontFamily) {
|
||||||
|
results = append(results, checkResult{catFonts, "Normal Font", statusOK, fontFamily, "Available", url})
|
||||||
|
} else {
|
||||||
|
results = append(results, checkResult{
|
||||||
|
catFonts, "Normal Font", statusWarn,
|
||||||
|
fmt.Sprintf("'%s' not found", fontFamily),
|
||||||
|
"Font is not registered. Try running 'fc-cache -fv' or install the font.",
|
||||||
|
url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monospace Font Check
|
||||||
|
if hasFont(monoFontFamily) {
|
||||||
|
results = append(results, checkResult{catFonts, "Monospace Font", statusOK, monoFontFamily, "Available", 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
|
||||||
|
}
|
||||||
|
|||||||
+152
-6
@@ -2,7 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -192,6 +194,7 @@ func runShellInteractive(session bool) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
ensureFontCache()
|
||||||
log.Infof("Spawning quickshell with -p %s", configPath)
|
log.Infof("Spawning quickshell with -p %s", configPath)
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, "qs", "-p", configPath)
|
cmd := exec.CommandContext(ctx, "qs", "-p", configPath)
|
||||||
@@ -227,8 +230,10 @@ func runShellInteractive(session bool) {
|
|||||||
|
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
tracker := &stderrTracker{parent: os.Stderr}
|
||||||
|
cmd.Stderr = tracker
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
log.Fatalf("Error starting quickshell: %v", err)
|
log.Fatalf("Error starting quickshell: %v", err)
|
||||||
}
|
}
|
||||||
@@ -277,7 +282,9 @@ func runShellInteractive(session bool) {
|
|||||||
case <-errChan:
|
case <-errChan:
|
||||||
cancel()
|
cancel()
|
||||||
os.Remove(socketPath)
|
os.Remove(socketPath)
|
||||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
exitCode := getProcessExitCode(cmd.ProcessState)
|
||||||
|
logStartupFailure(startTime, exitCode, tracker)
|
||||||
|
os.Exit(exitCode)
|
||||||
case <-time.After(500 * time.Millisecond):
|
case <-time.After(500 * time.Millisecond):
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +301,9 @@ func runShellInteractive(session bool) {
|
|||||||
cmd.Process.Signal(syscall.SIGTERM)
|
cmd.Process.Signal(syscall.SIGTERM)
|
||||||
}
|
}
|
||||||
os.Remove(socketPath)
|
os.Remove(socketPath)
|
||||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
exitCode := getProcessExitCode(cmd.ProcessState)
|
||||||
|
logStartupFailure(startTime, exitCode, tracker)
|
||||||
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,6 +443,7 @@ func runShellDaemon(session bool) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
ensureFontCache()
|
||||||
log.Infof("Spawning quickshell with -p %s", configPath)
|
log.Infof("Spawning quickshell with -p %s", configPath)
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, "qs", "-p", configPath)
|
cmd := exec.CommandContext(ctx, "qs", "-p", configPath)
|
||||||
@@ -478,8 +488,10 @@ func runShellDaemon(session bool) {
|
|||||||
|
|
||||||
cmd.Stdin = devNull
|
cmd.Stdin = devNull
|
||||||
cmd.Stdout = devNull
|
cmd.Stdout = devNull
|
||||||
cmd.Stderr = devNull
|
tracker := &stderrTracker{parent: devNull}
|
||||||
|
cmd.Stderr = tracker
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
log.Fatalf("Error starting daemon: %v", err)
|
log.Fatalf("Error starting daemon: %v", err)
|
||||||
}
|
}
|
||||||
@@ -528,7 +540,9 @@ func runShellDaemon(session bool) {
|
|||||||
case <-errChan:
|
case <-errChan:
|
||||||
cancel()
|
cancel()
|
||||||
os.Remove(socketPath)
|
os.Remove(socketPath)
|
||||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
exitCode := getProcessExitCode(cmd.ProcessState)
|
||||||
|
logStartupFailure(startTime, exitCode, tracker)
|
||||||
|
os.Exit(exitCode)
|
||||||
case <-time.After(500 * time.Millisecond):
|
case <-time.After(500 * time.Millisecond):
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,7 +557,9 @@ func runShellDaemon(session bool) {
|
|||||||
cmd.Process.Signal(syscall.SIGTERM)
|
cmd.Process.Signal(syscall.SIGTERM)
|
||||||
}
|
}
|
||||||
os.Remove(socketPath)
|
os.Remove(socketPath)
|
||||||
os.Exit(getProcessExitCode(cmd.ProcessState))
|
exitCode := getProcessExitCode(cmd.ProcessState)
|
||||||
|
logStartupFailure(startTime, exitCode, tracker)
|
||||||
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -748,3 +764,133 @@ func printIPCHelp() {
|
|||||||
fmt.Printf(" %-16s %s\n", targetName, strings.Join(funcNames, ", "))
|
fmt.Printf(" %-16s %s\n", targetName, strings.Join(funcNames, ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensureFontCache rebuilds the fontconfig cache if user-configured fonts are missing while skipping defaults
|
||||||
|
func ensureFontCache() {
|
||||||
|
if _, err := exec.LookPath("fc-list"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("fc-cache"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var fontsToCheck []string
|
||||||
|
|
||||||
|
if configDir, err := os.UserConfigDir(); err == nil {
|
||||||
|
settingsPath := filepath.Join(configDir, "DankMaterialShell", "settings.json")
|
||||||
|
if data, err := os.ReadFile(settingsPath); err == nil {
|
||||||
|
var settings struct {
|
||||||
|
FontFamily string `json:"fontFamily"`
|
||||||
|
MonoFontFamily string `json:"monoFontFamily"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &settings); err == nil {
|
||||||
|
if settings.FontFamily != "" && settings.FontFamily != "Inter Variable" {
|
||||||
|
fontsToCheck = append(fontsToCheck, settings.FontFamily)
|
||||||
|
}
|
||||||
|
if settings.MonoFontFamily != "" && settings.MonoFontFamily != "Fira Code" {
|
||||||
|
fontsToCheck = append(fontsToCheck, settings.MonoFontFamily)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fontsToCheck) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := exec.Command("fc-list", ":", "family").Output()
|
||||||
|
if err != nil || len(strings.TrimSpace(string(output))) == 0 {
|
||||||
|
log.Warnf("Font cache appears empty or corrupt, rebuilding...")
|
||||||
|
rebuildFontCache()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheFonts := strings.ToLower(string(output))
|
||||||
|
var missing []string
|
||||||
|
for _, font := range fontsToCheck {
|
||||||
|
if !fontInCache(strings.ToLower(font), cacheFonts) {
|
||||||
|
missing = append(missing, font)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missing) > 0 {
|
||||||
|
log.Warnf("Font(s) not found in cache: %s — rebuilding...", strings.Join(missing, ", "))
|
||||||
|
rebuildFontCache()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fontInCache(target, cache string) bool {
|
||||||
|
for _, line := range strings.Split(cache, "\n") {
|
||||||
|
for _, fam := range strings.Split(strings.TrimSpace(line), ",") {
|
||||||
|
if strings.TrimSpace(fam) == target {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func rebuildFontCache() {
|
||||||
|
cmd := exec.Command("fc-cache", "-f")
|
||||||
|
if output, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
log.Warnf("Failed to rebuild font cache: %v\n%s", err, string(output))
|
||||||
|
} else {
|
||||||
|
log.Infof("Font cache rebuilt successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type stderrTracker struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
buf strings.Builder
|
||||||
|
parent io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stderrTracker) Write(p []byte) (n int, err error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if s.buf.Len() < 8192 {
|
||||||
|
s.buf.Write(p)
|
||||||
|
}
|
||||||
|
if s.parent != nil {
|
||||||
|
return s.parent.Write(p)
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stderrTracker) String() string {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return s.buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// logStartupFailure logs diagnostic advice if qs crashes within 5s of launch.
|
||||||
|
func logStartupFailure(startTime time.Time, exitCode int, tracker *stderrTracker) {
|
||||||
|
if time.Since(startTime) >= 5*time.Second || exitCode == 0 || exitCode > 128 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if containsFontCrashSignature(tracker.String()) {
|
||||||
|
log.Errorf("DMS startup failed due to a potential font/rendering crash. Try running 'fc-cache -fv' and restarting DMS.")
|
||||||
|
} else {
|
||||||
|
log.Errorf("DMS startup failed (exit code %d). Run 'dms doctor' for more diagnostics.", exitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsFontCrashSignature(logStr string) bool {
|
||||||
|
logStr = strings.ToLower(logStr)
|
||||||
|
signatures := []string{
|
||||||
|
"fontconfig",
|
||||||
|
"freetype",
|
||||||
|
"ft_load_glyph",
|
||||||
|
"ft_face",
|
||||||
|
"fc-list",
|
||||||
|
"fc-cache",
|
||||||
|
"glyph",
|
||||||
|
"typeface",
|
||||||
|
}
|
||||||
|
for _, sig := range signatures {
|
||||||
|
if strings.Contains(logStr, sig) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -594,6 +594,7 @@ function getValidKeys() {
|
|||||||
|
|
||||||
function set(root, key, value, saveFn, hooks) {
|
function set(root, key, value, saveFn, hooks) {
|
||||||
if (!(key in SPEC)) return;
|
if (!(key in SPEC)) return;
|
||||||
|
if (value === undefined || value === null) value = SPEC[key].def;
|
||||||
root[key] = value;
|
root[key] = value;
|
||||||
var hookName = SPEC[key].onChange;
|
var hookName = SPEC[key].onChange;
|
||||||
if (hookName && hooks && hooks[hookName]) {
|
if (hookName && hooks && hooks[hookName]) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ Item {
|
|||||||
var fontName2 = availableFonts[j];
|
var fontName2 = availableFonts[j];
|
||||||
if (fontName2.startsWith("."))
|
if (fontName2.startsWith("."))
|
||||||
continue;
|
continue;
|
||||||
if (fontName2 === SettingsData.defaultMonoFontFamily)
|
if (fontName2 === Theme.defaultMonoFontFamily)
|
||||||
continue;
|
continue;
|
||||||
var lowerName = fontName2.toLowerCase();
|
var lowerName = fontName2.toLowerCase();
|
||||||
if (lowerName.includes("mono") || lowerName.includes("code") || lowerName.includes("console") || lowerName.includes("terminal") || lowerName.includes("courier") || lowerName.includes("dejavu sans mono") || lowerName.includes("jetbrains") || lowerName.includes("fira") || lowerName.includes("hack") || lowerName.includes("source code") || lowerName.includes("ubuntu mono") || lowerName.includes("cascadia")) {
|
if (lowerName.includes("mono") || lowerName.includes("code") || lowerName.includes("console") || lowerName.includes("terminal") || lowerName.includes("courier") || lowerName.includes("dejavu sans mono") || lowerName.includes("jetbrains") || lowerName.includes("fira") || lowerName.includes("hack") || lowerName.includes("source code") || lowerName.includes("ubuntu mono") || lowerName.includes("cascadia")) {
|
||||||
|
|||||||
@@ -226,13 +226,13 @@ Item {
|
|||||||
text: I18n.tr("Monospace Font")
|
text: I18n.tr("Monospace Font")
|
||||||
description: I18n.tr("Select monospace font for process list and technical displays")
|
description: I18n.tr("Select monospace font for process list and technical displays")
|
||||||
options: root.fontsEnumerated ? root.cachedMonoFamilies : ["Default"]
|
options: root.fontsEnumerated ? root.cachedMonoFamilies : ["Default"]
|
||||||
currentValue: SettingsData.monoFontFamily === SettingsData.defaultMonoFontFamily ? "Default" : (SettingsData.monoFontFamily || "Default")
|
currentValue: SettingsData.monoFontFamily === Theme.defaultMonoFontFamily ? "Default" : (SettingsData.monoFontFamily || "Default")
|
||||||
enableFuzzySearch: true
|
enableFuzzySearch: true
|
||||||
popupWidthOffset: 100
|
popupWidthOffset: 100
|
||||||
maxPopupHeight: 400
|
maxPopupHeight: 400
|
||||||
onValueChanged: value => {
|
onValueChanged: value => {
|
||||||
if (value === "Default")
|
if (value === "Default")
|
||||||
SettingsData.set("monoFontFamily", SettingsData.defaultMonoFontFamily);
|
SettingsData.set("monoFontFamily", Theme.defaultMonoFontFamily);
|
||||||
else
|
else
|
||||||
SettingsData.set("monoFontFamily", value);
|
SettingsData.set("monoFontFamily", value);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user