mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2025-12-06 06:36:15 +00:00
265 lines
6.6 KiB
QML
265 lines
6.6 KiB
QML
pragma Singleton
|
|
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import qs.Commons
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
property ListModel availableFonts: ListModel {}
|
|
property ListModel monospaceFonts: ListModel {}
|
|
property ListModel displayFonts: ListModel {}
|
|
property bool fontsLoaded: false
|
|
property bool isLoading: false
|
|
|
|
// Use objects for O(1) lookup instead of arrays
|
|
property var fontconfigMonospaceFonts: ({})
|
|
|
|
// Cache for font classification to avoid repeated checks
|
|
property var fontCache: ({})
|
|
|
|
// Chunk size for async processing
|
|
readonly property int chunkSize: 100
|
|
|
|
// -------------------------------------------
|
|
function init() {
|
|
Logger.i("Font", "Service started");
|
|
loadFontconfigMonospaceFonts();
|
|
}
|
|
|
|
function loadFontconfigMonospaceFonts() {
|
|
fontconfigProcess.command = ["fc-list", ":mono", "family"];
|
|
fontconfigProcess.running = true;
|
|
}
|
|
|
|
function loadSystemFonts() {
|
|
if (isLoading)
|
|
return;
|
|
Logger.d("Font", "Loading system fonts...");
|
|
isLoading = true;
|
|
|
|
var fontFamilies = Qt.fontFamilies();
|
|
|
|
// Pre-sort fonts before processing to ensure consistent order
|
|
fontFamilies.sort(function (a, b) {
|
|
return a.localeCompare(b);
|
|
});
|
|
|
|
// Clear existing models
|
|
availableFonts.clear();
|
|
monospaceFonts.clear();
|
|
displayFonts.clear();
|
|
fontCache = {};
|
|
|
|
// Process fonts in chunks to avoid blocking
|
|
processFontsAsync(fontFamilies, 0);
|
|
}
|
|
|
|
function processFontsAsync(fontFamilies, startIndex) {
|
|
var endIndex = Math.min(startIndex + chunkSize, fontFamilies.length);
|
|
var hasMore = endIndex < fontFamilies.length;
|
|
|
|
// Batch arrays to append all at once (much faster than individual appends)
|
|
var availableBatch = [];
|
|
var monospaceBatch = [];
|
|
var displayBatch = [];
|
|
|
|
for (var i = startIndex; i < endIndex; i++) {
|
|
var fontName = fontFamilies[i];
|
|
if (!fontName || fontName.trim() === "")
|
|
continue;
|
|
|
|
// Add to available fonts
|
|
var fontObj = {
|
|
"key": fontName,
|
|
"name": fontName
|
|
};
|
|
availableBatch.push(fontObj);
|
|
|
|
// Check monospace (with caching)
|
|
if (isMonospaceFont(fontName)) {
|
|
monospaceBatch.push(fontObj);
|
|
}
|
|
|
|
// Check display font (with caching)
|
|
if (isDisplayFont(fontName)) {
|
|
displayBatch.push(fontObj);
|
|
}
|
|
}
|
|
|
|
// Batch append to models
|
|
batchAppendToModel(availableFonts, availableBatch);
|
|
batchAppendToModel(monospaceFonts, monospaceBatch);
|
|
batchAppendToModel(displayFonts, displayBatch);
|
|
|
|
if (hasMore) {
|
|
// Continue processing in next frame
|
|
Qt.callLater(function () {
|
|
processFontsAsync(fontFamilies, endIndex);
|
|
});
|
|
} else {
|
|
// Finished loading all fonts
|
|
finalizeFontLoading();
|
|
}
|
|
}
|
|
|
|
function batchAppendToModel(model, items) {
|
|
for (var i = 0; i < items.length; i++) {
|
|
model.append(items[i]);
|
|
}
|
|
}
|
|
|
|
function finalizeFontLoading() {
|
|
fontsLoaded = true;
|
|
isLoading = false;
|
|
Logger.d("Font", "Loaded", availableFonts.count, "fonts:", monospaceFonts.count, "monospace,", displayFonts.count, "display");
|
|
}
|
|
|
|
function isMonospaceFont(fontName) {
|
|
// Check cache first
|
|
if (fontCache.hasOwnProperty(fontName)) {
|
|
return fontCache[fontName].isMonospace;
|
|
}
|
|
|
|
var result = false;
|
|
|
|
// O(1) lookup using object instead of indexOf
|
|
if (fontconfigMonospaceFonts.hasOwnProperty(fontName)) {
|
|
result = true;
|
|
} else {
|
|
// Fallback: check for basic monospace patterns
|
|
var lowerFontName = fontName.toLowerCase();
|
|
if (lowerFontName.includes("mono") || lowerFontName.includes("monospace")) {
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
// Cache the result
|
|
if (!fontCache[fontName]) {
|
|
fontCache[fontName] = {};
|
|
}
|
|
fontCache[fontName].isMonospace = result;
|
|
|
|
return result;
|
|
}
|
|
|
|
function isDisplayFont(fontName) {
|
|
// Check cache first
|
|
if (fontCache.hasOwnProperty(fontName) && fontCache[fontName].hasOwnProperty('isDisplay')) {
|
|
return fontCache[fontName].isDisplay;
|
|
}
|
|
|
|
var result = false;
|
|
var lowerFontName = fontName.toLowerCase();
|
|
|
|
if (lowerFontName.includes("display") || lowerFontName.includes("headline") || lowerFontName.includes("title")) {
|
|
result = true;
|
|
}
|
|
|
|
// Cache the result
|
|
if (!fontCache[fontName]) {
|
|
fontCache[fontName] = {};
|
|
}
|
|
fontCache[fontName].isDisplay = result;
|
|
|
|
return result;
|
|
}
|
|
|
|
function sortModel(model) {
|
|
// Convert to array
|
|
var fontsArray = [];
|
|
for (var i = 0; i < model.count; i++) {
|
|
fontsArray.push({
|
|
"key": model.get(i).key,
|
|
"name": model.get(i).name
|
|
});
|
|
}
|
|
|
|
// Sort
|
|
fontsArray.sort(function (a, b) {
|
|
return a.name.localeCompare(b.name);
|
|
});
|
|
|
|
// Clear and rebuild
|
|
model.clear();
|
|
batchAppendToModel(model, fontsArray);
|
|
}
|
|
|
|
function addFallbackFonts(model, fallbackFonts) {
|
|
// Build a set of existing fonts for O(1) lookup
|
|
var existingFonts = {};
|
|
for (var i = 0; i < model.count; i++) {
|
|
existingFonts[model.get(i).name] = true;
|
|
}
|
|
|
|
var toAdd = [];
|
|
for (var j = 0; j < fallbackFonts.length; j++) {
|
|
var fontName = fallbackFonts[j];
|
|
if (!existingFonts[fontName]) {
|
|
toAdd.push({
|
|
"key": fontName,
|
|
"name": fontName
|
|
});
|
|
}
|
|
}
|
|
|
|
if (toAdd.length > 0) {
|
|
batchAppendToModel(model, toAdd);
|
|
sortModel(model);
|
|
}
|
|
}
|
|
|
|
function searchFonts(query) {
|
|
if (!query || query.trim() === "")
|
|
return availableFonts;
|
|
|
|
var results = [];
|
|
var lowerQuery = query.toLowerCase();
|
|
|
|
for (var i = 0; i < availableFonts.count; i++) {
|
|
var font = availableFonts.get(i);
|
|
if (font.name.toLowerCase().includes(lowerQuery)) {
|
|
results.push(font);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
// Process for fontconfig commands
|
|
Process {
|
|
id: fontconfigProcess
|
|
running: false
|
|
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
if (this.text !== "") {
|
|
var lines = this.text.split('\n');
|
|
// Use object for O(1) lookup instead of array
|
|
var monospaceLookup = {};
|
|
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var line = lines[i].trim();
|
|
if (line && line !== "") {
|
|
monospaceLookup[line] = true;
|
|
}
|
|
}
|
|
|
|
fontconfigMonospaceFonts = monospaceLookup;
|
|
}
|
|
loadSystemFonts();
|
|
}
|
|
}
|
|
|
|
onExited: function (exitCode, exitStatus) {
|
|
if (exitCode !== 0) {
|
|
fontconfigMonospaceFonts = {};
|
|
}
|
|
loadSystemFonts();
|
|
}
|
|
}
|
|
}
|